我是靠谱客的博主 苹果嚓茶,最近开发中收集的这篇文章主要介绍2019java后端面试看这篇就够了(二)Java面试最值得收藏的文章(共4部分):为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?6.线程7、 解析xml的几种技术优化事务14.jvmJava基础,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Java面试最值得收藏的文章(共4部分):

2019java后端面试集合篇最值得收藏的(一)

2019java后端面试集合篇最值得收藏的(二)

2019java后端面试集合篇最值得收藏的(三)

2019java后端面试集合篇最值得收藏的(四)

Junit单元测试:

我在编写完自己的功能模块后,为了保证代码的准确性,一般都会使用junit进行单元测试,

当时使用的是junit4这种基于注解的方式来进行单元测试。为了和spring集成获取配置的bean,

通常使用 @RunWith来加载springjunit这个核心类,使用 @ContextConfiguration来加载

相关的配置的文件,通过 @Resource按名字来注入具体的bean,最后在需要测试的方法上面加上

@Test 来进行单元测试。并且在编写单元测试的时候还要遵守一定的原则如:

源代码和测试代码需要分开;测试类和目标源代码的类应该位于同一个包下面,即它们的包名应该一样;

测试的类名之前或之后加上@Test,测试的方法名通常也以test开头。

@RunWith(SpringJUnit4ClassRunner.class)//运行spring相关环境 相当于spring监听功能

@ContextConfiguration(locations={"classpath:spring-common.xml","classpath:spring-datasource.xml"})//读取spring配置文件 不识别* 只能识别具体文件 多个配置文件使用string数据传递

public class TestSpring {

  //注入Service层

private @Resource UserService userService;

@Test

public void testFind(){

List<User> userList = userService.findAllUserInfo();

for (User user : userList) {

System.err.println(user.toString());

}

}

}

SSM整合的流程【问到SSI整合流程就说我们通常用的是SSM,我就给您说下SSM的流程吧】

在项目中通过在web.xml配置springMVC的核心控制器DispatcherServlet并加载Spring-mvc-controller.xml,并且通过配置Spring的监听器contextLoaderListener加载spring-common.xml,之后新建控制层并在类上加入@Controller和@RequestMapping注解,并通过@Resouce注入service层,在

service的实现类上加入@Service注解并通过@Autowired注入dao层,dao层只有接口并没有实现类,是通过在mybatis中对应的含有sql语句的xml文件中来通过namespace指明要实现的dao层的接口,并使sql语句的id和dao层接口中的方法名一致从而明确调用指定dao层接口时要执行的sql语句。并且在spring-mvc-controller.xml中配置了component-scan对controller进行扫描从而使控制层的注解生效还配置了内部视图解析器从而在控制层进行页面跳转时加上指定的前缀和后缀,在spring-common.xml中配置了dbcp数据库连接池以及sqlSession来加载mapper下所有的xml并对所有的mapper层进行扫描也就是对dao层的扫描,

还通过Aop中的切点表达式对service层进行事务控制,并且对service层进行扫描使其注解生效。

 

SSH整合的流程

在项目中首先是通过在web.xml中配置strtus2的前端控制器filterDispatcher加载struts.xml配置文件并对指定的后缀名进行拦截,并且通过配置spring的监听器contextLoadListener加载spring的相关配置文件如

spring-service.xml,spring-dao.xml,spring-common.xml,之后新建控制层的类继承于BaseAction,而BaseAction继承于ActionSupport,在BaseAction中封装了常用的方法如getRealPath(),outJson()等,

之后控制层注入service层,service层注入dao,dao层继承于HibernateDaoSupport并注入spring-common.xml中配置的sessionFactory,sessionFactory注入dataSource连接数据库,注入hibernate.cfg.xml从而加载hbm.xml文件,除此之外还通过Spring中的Aop配置了事务并且通过切点表达式对Servcie层代码进行控制。

 

SQL语句的执行顺序:

查询中用到的关键词主要包含六个,并且他们的顺序依次为 

select--from--where--group by--having--order by 

 

其中select和from是必须的,其他关键词是可选的,这六个关键词的执行顺序 

与sql语句的书写顺序并不是一样的,而是按照下面的顺序来执行 

from--where--group by--having--select--order by, 

from:需要从哪个数据表检索数据 

where:过滤表中数据的条件 

group by:如何将上面过滤出的数据分组 

having:对上面已经分组的数据进行过滤的条件  

select:查看结果集中的哪个列,或列的计算结果 

order by :按照什么样的顺序来查看返回的数据 

 

HashMap的底层代码/原理【http://zhangshixi.iteye.com/blog/672697

HashMap底层就是一个数组结构,数组中的每一项又是一个链表。

当新建一个HashMap的时候,就会初始化一个数组。

 

Entry就是数组中的元素,每个 Entry 其实就是一个key-value对,

它持有一个指向下一个元素的引用,这就构成了链表。

 

 

HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。

HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,

当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,

再根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,

也会根据hash算法找到其在数组中的存储位置,

再根据equals方法从该位置上的链表中取出该Entry。

 

默认是构建一个初始容量为 16,负载因子为 0.75 的 HashMap。

 

也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,

就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,

而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,

那么预设元素的个数能够有效的提高HashMap的性能。

 

 

Http协议原理:

HTTP是一个超文本传输协议,属于OSI七层模型的应用层,由请求和响应构成,

是一个标准的客户端服务器模型。HTTP是无状态的也就是说同一个客户端的这次请求和上次请求是没有对应关系。

 

http的工作流程:

当发送一个http请求时,首先客户机和服务器会建立连接,

之后发送请求到服务器,请求中包含了要访问的url地址,请求的方式(get/post),

以及要传递的参数和头信息,服务器接到请求后会进行响应,

包括状态行,状态码,响应头,以及要响应的主体内容。客户端接收

到请求后将其展示到浏览器上然后断开和服务器端的连接。

 

简单说就是:建立连接--》发送请求--》响应--》断开连接

 

 

GET和POST的区别:


1、GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditPosts.jsp?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的Body中。
2、GET提交的数据大小有限制,最多只能有1024字节(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制。
3、通常用Post进行文件上传而get是不支持的。
4、GET方式没有POST方式安全

 

 

什么是长连接、短连接?

在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,

但任务结束就中断连接。

 

从 HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头有加入这行代码:

Connection:keep-alive 

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,

如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。

Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。

实现长连接要客户端和服务端都支持长连接。

 

HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。

 

Hashtable与HashMap的区别

HashMap不是线程安全的,HashTable是线程安全。

HashMap允许空(null)的键和值(key),HashTable则不允许。

HashMap性能优于Hashtable。

 

Map

1.Map是一个以键值对存储的接口。Map下有两个具体的实现,分别是HashMap和HashTable.

2.HashMap是线程非安全的,HashTable是线程安全的,所以HashMap的效率高于HashTable.

3.HashMap允许键或值为空,而HashTable不允许键或值为空

 

说说你对缓存的理解

通常情况下缓存是为了提高应用程序的性能,减少数据库的访问次数,缓存的存储介质可以

内存或者硬盘,通常将数据存储在内存里,确切的说是jvm的内存中,缓存是

基于Map这种思想构建的,以键值对的方式进行存取,之所以还可以将

缓存的数据存储在硬盘中,是因为内存资源相当有限和宝贵,所以当内存资源

不足的时候,就可以将其存储到硬盘中,虽然硬盘的存取速度比内存要慢,但是

因为减少了网络通信量,所以还是提高程序的性能。缓存可以分为客户端缓存和

服务器端缓存,所谓的客户端缓存通常指的是IE浏览器的缓存,服务器端缓存指

的web服务器的缓存,通常可以通过第三方组件实现,如oscache或者redis

 

我们通常将那些频繁访问但是又不是经常改变的数据进行缓存。为了保证缓存数据的

有效性,在数据发生改变的时候,我们要刷新缓存,避免脏数据的出现。刷新缓存的

策略有两种,一种是定时刷新,一种手动刷新。

缓存的层次如下:jsp-->action-->service(通常放置在service)-->dao,

缓存越靠前对性能的提升越大

 

缓存的策略:(缓存空间不足需要进行清理的时候使用)

LRU:最近最少使用原则.(理解:存储书)

FIFO:先进先出的缓存策略.(理解:排队)

 

你对Maven是怎么理解的

maven是一个项目管理工具,其核心特点就是通过

maven可以进行包的依赖管理,保证jar包版本的一致性,以及可以使多个项目共享

jar包,从而能够在开发大型j2ee应用的时候,减小项目的大小, 

maven根据“约定优于配置”的特性,对其项目的编译打包部署进行了

更为抽象的封装,直接使用系统预定好的mvn clean,compile,test,package等命令进行项目的操作。

为了保证团队中的成员能够节省下载jar包所需要的时间,

于是我就采用nexus搭建了在局域网内的maven私服,然后通过配置settings.xml中

建立mirror镜像,将所有下载jar包的请求都转发到maven私服上,之后通过在pom.xml

即(project object model)中配置项目所依赖的jar包,从而达到在构建项目的时候,

先从本地仓库中查找,如果不存在从内部私服查找,如果不存在最后再从外网central

服务器查找的机制,达到了节省下载带宽,提高开发效率,以及jar包重用的目的。

 

Spring MVC中的注解你都用过哪些

SpringMVC中用到过的注解有@RequestParam它的作用是接受前台传递的参数并且可以通过defaultValue属性对其设置默认值;在SpringMVC进行文件上传的时候也会通过@RequestParam和MultipartFile结合使用。@Autowired注解和@Resource注解的作用都是为了进行属性注入,但@Autowired默认是按照类型进行匹配,它是Spring提供的注解,@Resource默认是按照名字进行匹配,它是java提供的注解。在进行restful接口编程的时候我们还会用到@pathvariable注解从路径中获取参数信息以及用到@ResponseBody注解将实体类自动转换为指定的json格式,@RequestBody将前台传递过来的json格式的数据转换为对应的javabean。除此之外还有@Controller,@Service,@Repository分别在控制层,业务逻辑层和持久层的实现类型添加。最后还有@RequestMapping注解在控制层的方法上添加从而将指定url和方法对应起来。

 

SpringMVC的运行原理是:

整个处理过程从一个HTTP请求开始:

1.Tomcat在启动时加载解析web.xml,找到spring mvc的前端总控制器DispatcherServlet,并且通过DispatcherServlet来加载相关的配置文件信息。

2.DispatcherServlet接收到客户端请求,找到对应HandlerMapping,根据映射规则,找到对应的处理器(Handler)。

3.调用相应处理器中的处理方法,处理该请求后,会返回一个ModelAndView。

4.DispatcherServlet根据得到的ModelAndView中的视图对象,找到一个合适的ViewResolver(视图解析器),根据视图解析器的配置,DispatcherServlet将要显示的数据传给对应的视图,最后显示给用户。

 

谈谈你对Spring的理解

Spring就相当于一个粘合剂,有两个核心,一个核心是IOC (控制反转),它是基于工厂设计模式,所谓控制反转就是将自己手工完成对象创建(new)的这种任务交给spring容器去完成。和控制反转配套使用的还有一个DI也就是依赖注入。我们可以进行构造函数注入,属性注入等,最常用的还是属性注入。可以注入各种类型Map,List,properties

注入可以通过ByType和ByName分别按照类型和名字进行自动注入。

Spring中的Bean支持单例和原型两种方式,默认是单例的。

可以通过singleton=true/false来进行配置或者通过

scope="singleton",scope="prototype"来配置。

所谓单例:即至始至终在jvm中都只有一个该类的实例。

所谓原型:也叫多例,就每次都会创建一个新的对象实例。

 

另一个核心是AOP(面向切面编程/面向方面编程), AOP是OOP(面向对象编程)的延续,主要应用于日志记录,性能统计,安全控制,事务处理等方面。它是基于代理设计模式,而代理设计模式又分为静态代理和动态代理,静态代理比较简单就是一个接口,分别有一个真实实现和一个代理实现,而动态代理分为基于接口的jdk的动态代理和基于类的cglib的动态代理,Aop默认使用的是基于接口的jdk的动态代理。所谓动态代理,即通过代理类的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联。Jdk的动态代理要实现InvocationHandler接口并重写其中的invoke方法。

 

AOP的核心概念:

 

 

概念解释:

切面(Aspect): 有切点(PointCut)和通知(Advice)组成,它既包括横切逻辑的定义,也包括了连接点的定义。

切点(Pointcut):一个切点定位多个类中的多个方法。

通知也叫增强(Advice):由方位和横切逻辑构成,所谓的方位指的是前置通知,后置通知,返回后通知,环绕通知,抛出异常后通知

连接点(JoinPoint):由切点和方位构成,用来描述在在哪些类的指定方法之前或之后执行

所谓的方位包括:

前置通知(Before advice):在连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

 

返回后通知(After returning advice): 在连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

 

抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知

 

后置通知(After (finally) advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)

 

环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行

 

 

 

AOP在项目中的应用/后台的日志管理模块?[必说]

后台的日志管理模块是为了让开发人员根据记录的日志信息及时找到系统产生的错误以及知道当前系统在整个运行过程中都执行了哪些类的哪些方法。考虑到对日志的统一处理我就采用了AOP这项技术。

AOP的核心是切面,而切面中包含了切点和通知,切点是为了对指定类的指定方法进行拦截,通知中包含方位和横切逻辑,所谓方位指的是前置通知,后置通知,环绕通知等,横切逻辑指的是公共部分的代码如日志,事务等。

在项目中我们通常使用AOP进行事务方面的控制和日志的统一处理。在事务控制方面是通过Spring自带的事务管理器,配置切点表达式,对service层指定的方法如增删该进行事务控制,对查询进行只读事务控制从而提高性能。在日志的统一处理方面,我首先配置log4j.properties并指定日志级别为info,将日志输入到控制台以及指定的日志文件中。接着自己写一个日志的切面类LogAspect,并通过ProceedingJoinPoint【连接点】获取目标类名以及执行的方法名,通过调用LOG.info方法记录进入方法时的日志信息。为了记录出现异常时的错误日志,通过对proceed方法进行trycatch捕获,在catch中用LOG.error记录异常信息。在spring-mvc-controller.xml中配置aop-config,并通过aop-pointcut的切点表达式对所有的Controller和里面的方法进行拦截,最后通过aop-around配置环绕通知,并通过里面的method属性指明要调用切面类中的方法名。

 

#定义LOG输出级别

log4j.rootLogger=INFO,Console,File#定义日志输出目的地为控制台

log4j.appender.Console=org.apache.log4j.ConsoleAppender

log4j.appender.Console.Target=System.out#可以灵活地指定日志输出格式,下面一行是指定具体的格式

log4j.appender.Console.layout = org.apache.log4j.PatternLayout

log4j.appender.Console.layout.ConversionPattern=[%c] - %m%n

#文件大小到达指定尺寸的时候产生一个新的文件

log4j.appender.File = org.apache.log4j.RollingFileAppender#指定输出目录

log4j.appender.File.File = logs/ssm.log#定义文件最大大小

log4j.appender.File.MaxFileSize = 10MB# 输出所以日志,如果换成DEBUG表示输出DEBUG以上级别日志

log4j.appender.File.Threshold = ALL

log4j.appender.File.layout = org.apache.log4j.PatternLayout

log4j.appender.File.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c]%m%n

 

前台的日志管理模块:【15k及其以上必说】

前台的日志模块的核心价值是为了统计用户的行为,方便进行用户行为分析。 考虑到对日志的统一处理以及前台访问量巨大导致的大并发和大数据量的问题。 当时是结合AOP和Mongodb来完成了这项功能。

 

存日志的时候是通过SpringAOP写了个切面类,之后在切面类可以获取用户使用的

浏览器的类型如是IE还是谷歌,还是火狐;

以及用户使用的设备类型如是手机,

还是说是平板,还是说是PC;包括用户浏览过的商品信息,用户购买的商品信息等。将这些信息通过封装好的

MongodbUtil将其插入到mongodb数据库中方便其他系统后续对其进行分析。

考虑到mongodb的高可用性我们搭建了3台mongodb数据库来实现副本集,这样

不仅可以达到故障自动转移的特性而且也可以通过读写分离提高性能,即便

主服务器宕机了,还会通过投票选举出下一个主服务器继续提供服务。考虑到

后续数据量的不断增加,为了方便扩容我们还建立了3个分片。

图片管理模块:【15k及其以上必说】

考虑到我们的项目最后要部署到多台tomcat通过nginx来实现负载均衡,

为了对项目中的文件以及图片进行统一的管理,我们就用mongodb来充当文件服务器。

对于大部分的单张图片和单个文件来说小于16M,所以我们就以常规的方式来将

文件转换为二进制的字节数组进行保存。考虑到高可用性以及为了应对后期随着文件数量的不断

增加而能够方便进行扩容,我们建立了3个分片并将分片和副本集做了整合,每个分片都是一个副本集,这样

不仅满足了大数据量的存储也避免了分片中单台机器导致的单点故障问题。考虑到可能要处理

大于16M的文件,所以又增加了支持大文件存储的gridfs,这样即便再大的文件也会被gridfs分解为多个

chunk进行存储。

 

Restful的概述以及项目中应用?【必说的】

近期开发的项目有一部分是需要给前端提供接口,本来是想通过webservice的形式来提供接口进行数据的

输出,但项目经理要求用restful这种ROA面向资源编程的形式开发接口进行数据的提供,所以我就到网上

查阅了相关的资料,因为我们的项目是基于SpringMVC的,所以最终我就采用了基于SpringMVC进行restful

接口的开发。用到的注解有 @RestController @PathVariable @RequestBody 这些,其中 @RestController

就相当于 @Controller 和 @ResponseBody的组合体,这样就可以避免在各个方法上加入 @ResponseBody注解了,@PathVariable是为了从路径中获取参数信息, @RequestBody 起到的作用就是将前端提交过来的json数据根据相关的配置文件利用jackson这个工具自动转换为对应的javabean实体。

 

再者restful是基于HTTP协议现有的Get动作进行查询,Post动作进行增加,Put动作进行修改,Delete动作进行删除。返回结果我们采用JSON格式的数据,其中用code表示状态码,用message表示提示信息,用data表示返回的数据。

 

最后我们为了保证restful接口的安全性,采用了基于token的认证方式,它的流程是

用户用密码登录成功后,服务器返回token给客户端,客户端将token保存在本地,发起后续的相关请求时,

将token发回给服务器,服务器检查token的有效性,有效则返回数据,若无效,分两种情况

一种是token错误,这时需要用户重新登录,获取正确的token;一种是token过期,

这时客户端需要再发起一次认证请求,获取新的token。我们的token是通过uuid来保证唯一性,并且将其

存入redis中保证性能,通过uuid来充当key,通过userName来充当值。

 

 

Set,List,Collection,Collections的区别

1.List和Set都是接口,他们都继承于接口Collection,List是一个有序的可重复的集合,而Set的无序的不可重复的集合。Collection是集合的顶层接口,Collections是一个封装了众多关于集合操作的静态方法的工具类,因为构造方法是私有的,所以不能实例化。

 

2.List接口实现类有ArrayList,LinkedList,Vector。ArrayList和Vector是基于数组实现的,所以查询的时候速度快,而在进行增加和删除的时候速度较慢LinkedList是基于链式存储结构,所以在进行查询的时候速度较慢但在进行增加和删除的时候速度较快。又因为Vector是线程安全的,所以他和ArrayList相比而言,查询效率要低。

 

StringBuffer StringBuilder String 区别

String       字符串常量   不可变  使用字符串拼接时是不同的2个空间

StringBuffer  字符串变量   可变   线程安全  字符串拼接直接在字符串后追加

StringBuilder 字符串变量   可变   非线程安全  字符串拼接直接在字符串后追加

 

1.StringBuilder执行效率高于StringBuffer高于String.

2.String是一个常量,是不可变的,所以对于每一次+=赋值都会创建一个新的对象,StringBuffer和StringBuilder都是可变的,当进行字符串拼接时采用append方法,在原来的基础上进行追加,所以性能比String要高,又因为StringBuffer  是 线程安全的而StringBuilder是线程非安全的,所以StringBuilder的效率高于StringBuffer.

3.对于大数据量的字符串的拼接,采用StringBuffer,StringBuilder.

 

抽象类与接口的区别

1.一个类只能进行单继承,但可以实现多个接口。

2.有抽象方法的类一定是抽象类,但是抽象类里面不一定有抽象方法;

  接口里面所有的方法的默认修饰符为public abstract,接口里的成员变量默认的修饰符为  pulbic static final。

关系:

接口和接口      继承

接口和抽象类    抽象类实现接口

类和抽象类      类继承抽象类

类和类          继承

 

你对jquery的理解

Jquery是一个js框架,拥有跨浏览器的特性,可以兼容各种浏览器,

可以使用它的append方法、remove方法、insertAfter方法操作文档对象、

通过id选择器$("#id")以及类选择器$(".class")还有标签选择器$("标签名")

可以选择DOM元素、通过fadeIn In以及fadeOut制作淡入淡出的动画效果、

通过bind来对指定元素绑定事件、通过$.get,$.post以及$.ajax发送ajax

异步请求提高了性能。

 

你对Aajax的理解

AJAX 全称: 异步JavaScript及 XML。

Ajax的核心是JavaScript中的XmlHttpRequest(XHR)。

使用ajax可以提高用户的体验度,进行异步数据传输从而

提高性能。ajax不能跨域。

 dataType:'jsonp',可以通过设置动态的<script>标签

 

 

所谓的不能

跨域就是不能跨多个网站(多个域名),不能跨多个项目。

可以通过jsonp来解决ajax跨域的问题,而jsonp的实质就是通过动态添加script标签来实现的。 

$.ajax({

url: some.php,

async: false,

success : function(){

}

});

 

Mongodb概述:

 

Mongodb是一个nosql数据库,我们在项目中通常用它来存储评论信息,

【评论id,商品id,标题,评分,内容,评论人信息,评论的发布时间】

因为每个商品都会有评论信息,而且某些热门商品的评论信息可能是数千条,

mongodb正好适用于这种大数据量、高并发、弱事务的互联网应用。考虑

到mongodb的高可用性我们搭建了3台mongodb数据库来实现副本集,这样

不仅可以达到故障自动转移的特性而且也可以通过读写分离提高性能,即便

主服务器宕机了,还会通过投票选举出下一个主服务器继续提供服务。

 

再者考虑到我们的项目最后要部署到多台tomcat通过nginx来实现负载均衡,

为了对项目中的文件以及图片进行统一的管理,我们就用mongodb来充当文件服务器。

对于大部分的单张图片和单个文件来说小于16M,所以我们就以常规的方式来

文件转换为二进制的字节数组进行保存。考虑到高可用性以及为了应对后期随着文件数量的不断

增加而能够方便进行扩容,我们建立了3个分片并将分片和副本集做了整合,每个分片都是一个副本集,这样

不仅满足了大数据量的存储也避免了分片中单台机器导致的单点故障问题。考虑到可能要处理

大于16M的文件,所以又增加了支持大文件存储的gridfs,这样即便再大的文件也会被gridfs分解为多个

chunk进行存储。

 

商品管理模块的开发,这个模块包含:

 

商品管理,商品分类管理,商品参数管理,商品属性管理,规格管理,品牌管理;

在数据库中涉及到的表有产品表,产品图片表,品牌表,分类表,产品参数组表,产品参数表,产品参数值表等。

通常先在商品分类管理模块中建立产品的分类,该分类是一个树形表结构,我通过全部取出数据并对取出的数据进行递归算法生成树形结构的数据,并将其数据渲染为具有层次嵌套关系的下拉列表以及表格控件从而在页面进行使用,因为在后台的很多地方都使用到分类信息,而分类信息又不容易发生变化,所以我就结合oscache将分类信息进行缓存,并封装到BaseController中,从而方便在其他Controller中直接调用该方法。

 

建立分类后可以在商品参数管理模块中建立和指定分类绑定的参数组以及参数信息,之后在添加产品时就可以选择产品所属的分类然后就会出现和该分类绑定的参数组以及参数信息供公司内部业务人员填写,保存后将参数信息保存到参数值表中,普通用户可以在 产品详情页 的 商品参数 中找到。

规格管理,属性管理的操作流程和上述流程类似。

 

报表统计(spring定时器+poi+javamail+echarts)

我在做报表统计这个模块的时候采用了Echarts这项技术,使用了柱状图,饼状图,组合图按照时间以及分类进行了各种统计(销售渠道,客户群体,客户偏好,信息来源,产品分类,产品月销量,季度销量,年销量),除此之外因为经理要求要在每月月底将指定分类的产品信息通过excel的形式发送到相关人员的邮箱中,所以我就采用了spring定时器+poi+javamail的形式完成了上述要求。

 

统计分析模块:

统计模块由 会员统计,店铺统计,销量统计等构成,

其中会员统计可以按天、周、月通过echarts的

折线图对新增的会员进行统计分析,通过echarts的饼状图

对会员所在区域进行统计,并且可以根据会员

的下单量,下单商品件数,下单金额分别进行统计分析

计算出买家排行top10,并支持将这会员信息通过poi导出

excel结合javamail发送到指定人的邮箱中。

 

webService:

我在做上个项目的时候库存调拨模块需要调用一些分部的库存资料。项目经理让我去完

成这个任务,我根据以往的项目经验,想到两种解决方案,第一种就是开放另外一个项

目的数据库的权限给我,然后我直接通过访问另外一个项目的数据库,来得到需要的信

,但是经过我后来的考虑,觉得安全隐患太大,因为当时这个项目是另外一家公司负

责在做,对于数据库里面的表结构,以及以后牵涉到的责任问题都很多。而且,所以我

就采用了第二种方案,即通过webservices的方式,进行异构系统之间数据信息的传

。经过查阅资料我了解到webservices有三种具体实现,xfire,cxf,axis2。虽

然axis2支持的语言比较多,但是它的性能相对于cxf要低。并且cxf是xfire的升级版

本,适用于java语言,和spring整合起来也比较方便,通过上面分析,结合我们目前的

两个项目都是基于java语言的,所以我采用cxf这种方式实现了两个项目之间数据的传

递,我们为了保证webservice的安全性我们采用了基于WS-Security标准的安全验证(使

用CXF回调函数)

Cxf的服务端配置流程是:并首先在web.xml里引入他/它的核心类cxfServlet,指定一个路径,表明对哪些路径进行拦截,之后我们在要发布的接口上加上@webService注解 ,而且还要在实现类添加同样的注解,并表明是实现了那个接口,之后在spring--webService.xml中发布webService服务,通过jaxws:endpoint这个标签来表明实现服务接口的类,以及

要发布的访问地址,配置完成之后在浏览器输入地址进行访问。

客户端的配置流程:

首先使用服务端发布的访问地址根据wsdl2java命令生成客户端的代码,将代码拷贝到项

目中,之后配置spring--webservice--client.xml,定义一个bean,通过address属性指明要访问的地址,通过serviceClass指明充当中间桥梁的服务类,这样就可以访问到服务端发布的服务类。

库存管理中的效期查询:

我做上个项目时,由于库存中的医疗器械有很多,查询起来会很麻烦。所以我们采用了

solr进行搜索,Solr是一个基于lucene全文搜索引擎全文检索是指计算机索引程序通

过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和

位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈

给用户的检索方式。这个过程类似于通过书的目录来查找相关的内容。在做模糊匹配的

时候,它可以用来替换like的模糊匹配,从而在匹配的准确性以及性能上得到了大大的

提高,用IK分词器进行分词,我用solr做到的一个最终效果就是分页,高亮显示,排

序,多字段,多条件的查询,在从数据库中取出数据生成索引的时候,遇到了内存溢出

的问题,所以我采用了分段批量提取的方式,这样很好的解决了内存溢出的问题,除此

之外对于后续增加的数据根据优先级的不同采用不同的策略,对于那些相对重要的数

据,我使用了增量索引使用spring定时器进行每30分钟增量生成一次,对于不太重要

的数据,在每天凌晨进行增量索引的生成。

 

 

员工登入的首页的信息:展示了各种器材的信息,器材是有很多种类的,但这些信息是不经常的改变的,所以就用这个器材的数据进行了oscache页面缓存,

接下来我说一下缓存:

一般我们使用缓存来达到提高应用程序的性能目的,我们都知道影响性能的瓶颈是数据库。使用缓存可以减少与数据库的交互次数。缓存的存储介质可以内存或者硬盘,通常将数据存储在内存里,确切的说是jvm的内存中,缓存是基于Map这种思想构建的,以键值对的方式进行存取,所以还可以将缓存的数据存储在硬盘中,是因为内存资源相当有限和宝贵,所以当内存资源不足的时候,就可以将其存储到硬盘中,虽然硬盘的存取速度比内存要慢,但是因为减少了网络通信量,所以还是提高程序的性能。缓存可以分为客户端缓存和服务器端缓存,所谓的客户端缓存通常指的是IE浏览器的缓存,主要用来存储css样式,静态图片。

服务器端缓存指的web服务器的缓存,通常用来存储一些经常使用,但是不经常改变的数据。通常可以通过第三方组件实现,如oscache,memcache,我们这里使用的是Oscache结合Spring + Hibernate + Springmvc框架,同时为了保证缓存数据的有效性我们采用了Spring定时器来刷新数据,避免了脏数据的出现。

 

 

负载均衡(服务器调优):

我们在做这个项目时,考虑到服务器性能的问题,最开始想到使用纵向扩展,来增加硬件的配置提高其性能,但这样做比较耗费资金,而且服务器内存空间也是有限的;所以后来就使用横向扩展来达到这一目的.

当时我们使用nginx+3个tomcat进行负载均衡,在我们不进行负载均衡之前,那所有的请求都由一台tomcat进行处理,这样会使我们的tomcat所承受的压力增大,而我们进行负载均衡之后,同样数量的请求经过nginx将其分发到多台tomcat进行处理,从而降低每台tomcat所承受的压力,而且当其中一台机器宕机时,其他机器还可以继续提供服务,保证服务不间断。(当时碰到了这么一个问题,用户第一次登陆成功以后,当再次登陆就报验证码错误,之后呢我就审查了一下代码,发现第一次登陆session存在了第一台服务器上,第二次登录session就跑到了第二台服务器上)为了解决这个问题,后来我查询了一下资料,知道这是负载均衡中session共享的问题,我们通过将session信息保存到redis服务器中,这样在多台tomcat中关于session的存取就都通过redis来完成。

Redis缓存

redis是一个基于key,value的支持多种数据类型的可进行持久化的内存数据库

我们在项目中应用的时候为了保证redis服务器的安全通常会在

redis.conf配置文件中绑定具体的ip地址这样只有该ip地址才能

访问redis服务器,并且设置长度为20位左右的密码,从而保证

只有进行了密码授权才能进行相关的操作

为了保证redis不会因为占用内存过大而导致系统宕机,通常在

将redis当做缓存服务器使用时,设置存储数据的过期时间,并且

通过设置maxmemory【最大内存】和maxmemory-policy【数据清除策略】为allkeys-lru来达到预期的效果

我们在项目中通常使用redis来充当缓存服务器来缓存分类列表,品牌列表,热销商品,推荐商品以及该商品的关联商品等等。

使用了jedis作为客户端,并考虑到性能问题使用了jedis连接池

考虑到redis服务器的高可用性,我们做了redis的主从复制,并且

通过加入哨兵来使redis主服务器宕机时,从服务器自动转换为主服务器继续提供服务。

 

客户管理webService

webservice是SOA面向服务编程(远程接口)目的是为了进行两个项目之间的数据传输而这两个项目开发语言可以不一样。它可以很好地进行异构系统之间数据信息的传递,同时页避免信息孤岛的问题,之所以能够跨语言,跨平台,跨项目是因为它们基于Xml进行数据的传输(根本因为Xml可以跨语言,跨平台)

webservice实现方式

cxf呢是xfile的升级版本,就好比是struts2是webwork的升级版,cxf和spring集成起来呢非常方便,简易。

Xfire:轻量级,性能高

Axic2:重量级,功能强,性能低

同时呢我也负责一些客户管理,当时我做这个客户管理的时候,客户方要求这个项目的一些客户信息数据要与另外一个项目进行相互访问

想到两种解决方案,第一种就是开放另外一个项目的数据库的权限给我,然后我直接通过访问另外一个项目的数据库,来得到需要的信息,但后来我分析了下,觉的这种方式不安全,而且因为当时这个项目是另外一个项目组负责在做,所以数据库里面的表结构,以及以后牵涉到的责任问题都很多。所以我就采用了第二种方案,即通过webservices的方式,它可以很好地进行异构系统之间数据信息的传递,同时页避免信息孤岛的问题。webservices有三种具体实现xfire,cxf,axis2,我根据以往的项目经验,了解axis2适用于多种语言,是一个重量级的框架,性能比较低。而Xfile相比于axis2来说是一个轻量级的框架,性能比axis2高。而cxf呢是xfile的升级版本,就好比是struts2是webwork的升级版,cxf和spring集成起来呢非常方便,简易。所以我采用cxf这种方式实现了两个项目之间数据的传递,我们为了保证webservice的安全性我们采用了基于WS-Security标准的安全验证,使用CXF回调函数。

为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?

  今年我一直在思考web开发里的前后端分离的问题,到了现在也颇有点心得了, 随着这个问题的深入,再加以现在公司很多web项目的控制层的技术框架由struts2迁移到springMVC,我突然有了一个新的疑问无法得到正确的 解释,为什么我们现在做java的web开发,会选择struts2或者springMVC这样的框架,而不是使用servlet加jsp这样的技术呢? 特别是现在我们web的前端页面都是使用velocity这样的模板语言进行开发,抛弃了jsp,这样的选择又会给我们java的web开发带来什么样的 好处,延着这个问题的思路,我又发现新的疑问,为什么现在很多java企业级开发都会去选择spring框架,spring框架给我们开发的应用带来了什 么?这么一想还真是问倒我了,我似乎很难找到一串能让人完全信服的答案,最终我发现,这些我认为我很熟悉很常用的技术,其实还有很多让我陌生不解的地方, 这些陌生和不解的地方也正是我是否能更高层次使用它们的关键,今天这篇文章我就来讲讲这些问题,不过struts2,spring这样的技术经过这么多年 的积累已经是相当庞大和复杂,它们的面很广,本人虽然已经用了它们多年,还是有很多技术不熟悉和不清楚,所以本文不是全面对我题目做出解答的文章,而是根 据我现有知识结构来理解这个问题。

  软件里有很多优秀的框架,有一种类型的框架,它的特点是建立在一个现有技术的基础上,提供和现有技术一样业务功能的技术框架,这个新的技术框架 比原技术更加易用,更加健壮同时功能更加强大,例如:jQuery,以及本文所要谈到的struts2和springMVC,深究这些框架都是相当之复 杂,但是它们的优点其实只有一个:就是让使用者只关心核心业务的开发,框架帮你屏蔽原有技术跟业务开发无关的各类技术问题。像 jQuery,struts2或springMVC这类框架之所以优秀,就是它们在这点上做的太好了,以至于很多使用它的程序员都已经不清楚原有技术的真 实面目,因此我们要将struts2理解的更好,使用的更加熟练和深入,这里我们就要跳出struts2的技术,到struts2技术的源头 servlet,仔细研究下servlet的特点,只有这样我们才能把struts2框架学的更好。

Servlet的作用接收浏览器传给服务端的请求(request),并将服务端处理完的响应(response)返回给用户的浏览器,浏览 器和服务端之间通过http协议进行沟通,其过程浏览器根据用户的选择将相关信息按http协议报文的规范组装请求的http报文,报文通过网络传输到 指定的服务器,服务器通过特定的web容器接收这个报文信息,例如:tomcat,jetty,jboss这样的web容器,web容器会将http报文 解析出来,如果是用户请求,最终解析出来的报文信息会用一个request对象存储起来,服务端使用这个request做完相应的处理后,服务端程序将结 果信息封装到response对象里,然后将response对象交给web容器,web容器则把这个response对象转变为http协议的报文,并 将报文回传给浏览器,浏览器最后解析这个响应报文,将最终结果展示给用户。

Web容器创造了servlet接口,servlet接口就是开发人员自己实现业务逻辑的地方,程序员开发servlet就好比做填空题,而填 空题的语境或者说上下文提示就是由request和response对象,但是javaEE规范里的servlet接口很简单,就三个方法 init,service和destory,但是这个接口太笼统,所以规范里还提供了一个HttpServlet类,这个类根据http请求类型提供了 doGet,doPost等方法servlet接口最大的特点就是根据http协议的特点进行定义,因此做servlet开发时候如果使用者对http 协议特点不是特别熟悉,都会碰到或多或少令人迷惑的问题,特别是碰到一些复杂特殊的请求时候:例如文件上传,返回特殊的文件格式到浏览器,这时候使用 servlet开发就不是很方便了,servlet开发还有个问题可能大家常常被忽视,就是请求的数据的类型转化,http协议传输都是文本形式,到了 web容器解析后也是文本类型,如果碰到货币,数字,日期这样的类型需要我们根据实际情况进行转化,如果页面传送的信息非常多,我们就不得不做大量类型转 化,这种工作没有什么技术含量,是个体力活而且很容易导致程序错误。同时java的企业开发都是围绕javabean进行,类型转化好的数据还要封装到对 应的javabean里,这种转来转去的事情对于项目开发绝对不是什么好事情,所以古老的struts1为这种问题找到了一种解决方案,就是定义了一个 DTO对象(数据传输对象),专门负责做这样的事情,不过到了struts2,整个替代servlet的action本身就是一个javabean。

Java的企业开发一个技术特点就是使用javabean进行的,struts2的特点之一就是它替代servlet的操作类就是一个典型的 javabean,首先struts2框架将页面传输的数据进行类型转化和封装后将请求信息封装到了这个javabean的属性里,这样我们开发web程 序时候就省去了烦心的类型转化和封装的问题,前面我讲到传统的servlet是根据http协议进行定义的,它会按你请求方式(post还是get方式) 来处理用户的请求,但是对于一名程序开发人员而言,一个请求,具体到一个url,其实对于服务端而言就是服务端对外提供的一个功能,或者说是服务端对外的 一个动作,如果我们使用servlet开发程序我们就得把http的动作转化为具体的业务动作,这就让程序开发变得繁琐,增强了开发的难度,所以 struts2替代servlet的javabean就屏蔽了servlet里http的请求方式和具体业务动作转化的问题,javabean里的每一个 方法都可以和每一个url请求一一对应,这必然减轻了开发的难度问题。

 

Servlet另一个作用就是构造response对象,让页面获得正确的响应,其实现代的浏览器是一个多媒体工具,文字,图片,视屏等等东西都可 以在浏览器里显示,资源的不同就会导致http响应报文的差别,如果我们使用servlet开发就要根据资源的不同在java程序里用硬编码的形式处理, 这样的程序很难复用,而且如果程序员对某种资源的处理理解不到位,就会导致问题的出现,struts2通过配置文件的形式将这样的逻辑从java程序里剥 离出来,使用配置的方式进行统一管理,这个做法和spring的AOP方式类似,这样就让结果处理方式更加统一,更加利于管理,同时也提升了程序的健壮性 以及降低了开发的难度。

Servlet在MVC开发模式里就是其中C层即控制层,控制层就像俄罗斯的双头鹰(一个头向东看一个头向西看)一样,一个头向M层模型层看, 一个头向V层视图层看,模型层也是用java编写的,控制层也属于服务端语言开发,所以M层和C层的沟通没有天然的障碍,但是和V层视图层就不一样了,这 是一个跨语言的沟通,对于浏览器,它只懂得html,javascript和css,浏览器是理解不了java这种语言的东西,但是要让服务端的东西能被 浏览器理解接受,我们就必须得把服务端的响应信息放到页面里,因此就需要一个技术把java的信息转化到html页面里,这就是javaEE规范里提供了 jsp技术,jsp其实是一种服务端技术而非客户端技术,不过它看起来似乎更像html技术,最早的jsp开发里都是直接将java代码写到页面里,这种 坏处谁都知道,之后javaEE规范提供了自定义标签技术,使用一种类似html标签的方式来解析java代码,struts2框架提供了一整套完整的自 定义标签技术,这似乎听起来不算啥,但是它的作用非凡,因为自定义标签之所以叫自定义就是每个人都可以自己来定义,如果没有一个规范必然产生混乱,而且一 套完善的自定义标签是个系统工程,一套完整的自定义标签相当于我们在自己定义一套新的开发语言,做程序的人听到这个一定就会明白开发一套完整的自定义标签 的工作量和开发难度都是难以想象的,而且自定义标签都是和控制层紧密相连,其难度又会增加一个维度,所以struts2提供的自定义标签对于业务开发带来 的将是质的飞越。

Servlet里还有两个重要的技术:监听器和过滤器,对于监听器在web开发里使用的场景比较少,都是一些十分特别的情况才会使用,大部分 web开发里可以忽略它的使用,我们用的最多的监听器可能就是对ServletContext创建和销毁的监听器,ServletContext是整个 web应用的全局对象,它和Web应用的生命周期绑定在一起,因此使用这个监听器对Web应用的全局信息进行初始化和销毁操作,例如spring容器的初 始化操作。比较有意思的是过滤器,在struts2里有个拦截器,它们的作用相同都是用来拦截请求的,因为拦截器是struts2的特有功能,在 struts2里使用拦截器自然比使用过滤器更顺手,其实拦截器所用的技术比过滤器更加先进,因为拦截器使用了反射技术,因此拦截器拦截的面更大,控制请 求的能力更强,它能完成的任务也会更加的丰富多彩。

  在我第一次接触struts2时候,有人告诉我struts设计的一个目的就是想屏蔽在控制层里操作request和response对象,因 为这两个http协议的儿子会造成web开发里思路的混乱,但是我在实际开发里却经常不自觉的使用这两个对象。而且本人做前端开发非常喜欢使用ajax, 使用ajax技术时候我就很讨厌struts2的自定义标签,我更加喜欢在页面里用javascript技术处理各种信息,最终struts2在我眼里就 是一个servlet的变体,因此曾经有段时间我常常在想是不是可以抛弃struts2,直接用servlet,因为struts2里用到了太多反射机 制,特别是使用注解做配置(注解是用反射实现的),在java里反射的执行效率是非常低的,直接使用servlet一定能提升web应用的执行效率。其实 这个倒很难做到,因为当时我没法在servlet里灵活的运用spring技术。

  下面我要谈谈spring了

spring技术可以说是java企业开发里最重要的技术,不过真的理解spring的作用和意义还真是一件麻烦的事情,很多人对spring 理解其实都是停留在使用阶段(例如:声明式事务很好用等等),当今的spring技术生态环境里可谓是蔚为壮观,spring已经包罗万象,它的内容之多 完全不亚于它的本源java语言了,而spring这么大的框都是建立在ioc和aop技术之上,只有深入理解了这两个技术我们才能明白为什么 spring这个框能装的下那么多东西了。

  首先是ioc,ioc技术第一个解释叫做控制反转,它还有个解释就是依赖注入,这两个名字很难从字面理解,但是当你理解它的原理后就会发现它们 的描述是何等准确。Ioc技术的本质就是构建对象的技术换句话说就是将一个类实例化成对象的技术,在java里实例化类通过new关键字进行的,每次 new一个类都会产生一个新的实例对象,这么做视乎很浪费,有时这种浪费还挺危险,因为在程序开发时候我们常常只需要某个类永远只能产生一个的实例对象这 个时候就得使用单例模式,此外在设计模式里还可以通过工厂方式产生对象,使用过spring的人看到上面的文字就知道了,spring里bean的定义就 和上面的内容一一对应,scope属性single产生单例对象,prototype产生新对象,bean还可以通过工厂方式产生对象,可以说 spring的bean就是制造对象的工具。面向对象编程里对象相当于显示生活中的一个实体,例如我们有个对象作用是完成打猎的操作,那么打猎这个对象内 部包含两个辅助对象:人和枪,只有人和枪赋予了打猎这个对象,那么打猎对象才能完成打猎的操作,但是构建一个人和枪的对象并不是看起来那么简单,这里以枪 为例,要创造一把枪我们需要金属,需要机床,需要子弹,而机床和子弹又是两个新对象,这些对象一个个相互嵌套相互关联,大伙试想下如果我们在java代码 里构建一个枪的对象那是何其的复杂,假如我们要构造的不是简单的枪对象而是更加复杂的航空母舰,那么构造这个对象的成本之高是让人难以想象的,怎么来消除 这种对象相互嵌套相互依赖的关系了?

spring提供了一种方式,这种方式就是spring提供一个容器,我们在xml文件里定义各个对象的依赖关系,由 容器完成对象的构建,当我们java代码里需要使用某个实例的时候就可以从容器里获取,那么对象的构建操作就被spring容器接管,所以它被称为控制反 转,控制反转的意思就是本来属于java程序里构建对象的功能交由容器接管依赖注入就是当程序要使用某个对象时候,容器会把它注入到程序里,这就叫做依 赖注入。在java开发里我们想使用某个类提供的功能,有两种方式,一种就是构造一个新的类,新的类继承该类,另一种方式则是将某个类定义在新类里,那么 两个类之间就建立一种关联关系,spring的ioc容器就是实现了这种关联关系(记住不是继承关系哦),那么某个类要被赋予到新类有哪些办法了?一般只 有两种:一种就是通过构造函数,一种就是通过setXXX方式,这也是spring容器使用到了两种标准的注入方式

  不管是上面说的继承方式,还是关联方式其实都是增强目标对象能力的开发手段,在设计模式里有一种代理模式,代理模式将继承模式和关联模式结合在 一起使用,代理模式就是继承模式和关联模式的综合体,不过这个综合体的作用倒不是解决对象注入的问题,而是为具体操作对象找到一个保姆或者是秘书,这就和 小说里的二号首长一样,这个二号首长对外代表了具体的实例对象,实例对象的入口和出口都是通过这个二号首长,因为具体的实例对象是一号首长,一号首长是要 干大事的,所以一些事务性,重复性的工作例如泡茶,安排车子,这样的工作是不用劳烦一号首长的大驾,而是二号首长帮忙解决的,这就是aop的思想,aop 解决程序开发里事务性,和核心业务无关的问题,但这些问题对于业务场景的实现是很有必要的,在实际开发里aop也是节省代码的一种方式。

Spring的核心技术的作用本质就是一个 沟通机制,spring总是尽全力的让沟通的双方信息畅通,同时降低双方的沟通成本,在现实机构里一个善于沟通的人肯定是该公司的领导,很会沟通的领导能 调动起各种资源的积极性,善于沟通的领导就会做到海纳百川,让各种不同人追随他,所以当今的spring就是一个大框,什么都可以往里装。

Spring很像银行,它不能直接创造物质财富,但是一切资源都要通过它进行流通,它能控制经济发展的走向,回到程序的世界,spring的作 用是被标榜为程序之间的解耦,spring能降低不同模块之间的耦合度,原因就是在程序开发里不同模块之间信息的沟通是通过对象传递完成的,而对象能否顺 利传递就是要合理的构建好对象,而管理好对象的构建方式就能管理好对象传递,这就是spring给系统架构设计带来的好处

Hashtable 与HashMap的区别

Hashtable是基于陈旧的Directory 而Hashmap 是AbstractMap 基于jdk1.2之后的Map接口

Hashtable Synchronize修饰同步 他是线程安全的他的底层代码中put的用法 是先进行判断如果为空抛一个异常空指针有contains。Hashmap 的效率稍微高一点有containsvalue和containsKey方法方法不是Synchronize的要提供外同步

在进行开发后台管理系统的时候遇到过什么问题?(自我发挥)

我感觉后台管理系统的难度并不是很大,而我遇到的问题总的来说有2个吧第一个问题就是需求经常发生变动,第二个就是我们在开发的过程中有过这样一个需求,在访问首页的时候有3/4的几率可以访问成功而那1/4的几率访问失败,当时我是有随机数Math中的random获取随机数的方法这个方法获取的数字是1-10随机的int类型然后进行判断如果数小于0.25则访问失败, 否则相反。

Jdk与jre的区别

Jdk是java的开发环境 而jre是java的运行环境这是每个开发人员众所周知的可是为什么jdk的根目录下还有一个jre这两个jre有什么区别呢?Jdk运行也需要在jre中所以,jdk中的jre是用来维护jdk的而jdk文件夹外面的jre是用来运行java编译完成的.class文件

为什么Connection 接口下的集合类都是可以遍历的?

Connection 下的所有子类都是可以遍历的因为List 、Set 都继承了Connection接口而这个接口也继承了Iterable这个接口这个接口中只有两个方法next 和hasnext(当中还有一个方法是remove())

Redis相关知识

1. 使用Redis有哪些好处?

(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

(2) 支持丰富数据类型,支持STRING(字符串)、LIST(列表)、SET(集合)、HASH(散列)和ZSET(有序集合)(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

 

2. redis相比memcached有哪些优势?

(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型

(2) redis的速度比memcached快很多

(3) redis可以持久化其数据

 

3. redis常见性能问题和解决方案:

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...

这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

Redis可以做异步redis持久化的时候只能存储到本地硬盘上2g内容默认过期时间无限制

在开发过程中poi遇到的问题总结

将数据转换成excel过程中我发现一个问题,当我执行导出操作时候大数据量的时候没有考虑过excel最大存放数据条数是多少我在网上搜索了一下大概是53200多条如果再多的话也可以存但是excel解析不了那么多的数据那个excel是死的打不开

用js代码区分浏览器的种类

Navigator 中有   JS获取浏览器信息
        浏览器代码名称:navigator.appCodeName
        浏览器名称:navigator.appName
        浏览器版本号:navigator.appVersion
        对Java的支持:navigator.javaEnabled()
        MIME类型(数组):navigator.mimeTypes
        系统平台:navigator.platform
        插件(数组):navigator.plugins
        用户代理:navigator.userAgent

Rest

是webservice的一种风格 对外提供接口以增删改查的方式进行提供这样耿方便一些正常我们使用webservice都是采用soap的风格的

Webservice 描述

在微信公众号与电商项目的接口交互中,电商项目作为服务端对外发布接口,微信公众号作为客户端调用接口。我们采用的WebService发布接口,具体实现是CXF框架。

WebService是一种跨语言跨平台的远程调用技术。webservice都是基于soap协议的,soap搜捕协议的本质就是http协议加上xml格式的协议体。说白了就是HTTP协议。

那么基于搜捕协议有什么优点呢?

他是面向对象的,它有WSDL,可以基于WSDL生成客户端调用的代码,这样我们就可以直接在客户端进行调用了,中间也不用什么复杂的json转换之类的。我们只需要做到面向对象开发就行了,但是你要知道,使用json字符串的效率是比较高的。Webservice就是系统之间通信的一个手段。

在j2ee领域中主流框架有axis2,xfire,cxf,这三种框架都是apache提供的。但是axis2已经很久不更新了,cxf是xfire的升级版本,并且cxf支持的协议也很多,而且他也能跟spirng很好的集成,所以我们在开发接口的时候采用的是cxf框架。

Webservice应用场景大部分都是在不同系统之间数据交互时运用。为了保证数据交互的安全性以及有效性,我们采用了数据加密和压缩。

加密:客户端的每一次请求,都要带着请求头,而服务端就去解析请求头,看里面带的token是否跟预期的一致,如果一致就说明安全了,否则就抛出异常不让调用。

压缩:在使用WebService技术的过程中,免不了进行数据传输,比如说传输文本、图片、Zip压缩包等。在网络环境比较好、数据量小的情况下,传输的方式可以忽略不计,我们可以选择任意的方式,不需要考虑优化的问题,可以将图片、包变成二进制流进行传输,可以从数据库读出数据来,以DataSet形式进行传输。

什么叫做单例模式

保证一个类仅有一个实例并提供一个全局访问点

一个类只有一个实例对外访问

私有化一个构造函数 对外提供一个共有的方法吧实例返回回去

修饰 static

 

 

 C3P0和DBCP


C3P0是Hibernate3.0默认的自带数据库连接池,DBCP是Apache开发的数据库连接池。我们对这两种连接池进行压力测试对比,发现在并发300个用户以下时,DBCP比C3P0平均时间快1秒左右。但在并发400个用户时,两者差不多。

速度上虽然DBCP比C3P0快些,但是有BUG:当DBCP建立的数据库连接,因为某种原因断掉后,DBCP将不会再重新创建新的连接,导致必须重新启动Tomcat才能解决问题。DBCP的BUG使我们决定采用C3P0作为数据库连接池。

数据库链接池和线程池是一样的就是把数据库链接和数据库挂接起来,然后放到一个容器,再来拿链接是,从容器里拿,来时,不会一次都给你,比如一共100个,一次给10个,然后在放十个,避免用完了不放进去。

6.线程

进程一个正在执行的程序,每个进程都有自己的执行顺序,和执行路径,或者叫控制单元;而线程就是进程里面的执行路径,每个进程的里面至少有一个线程,两个线程或两个以上的线程成为多线;而多线程的执行是靠争取cpu的执行权力,一次cpu只能执行一个线程,只不过是切换的快,我们所看到的是一块执行;多线程是多个代码同时执行;

    线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态

    1.新建状态(New): 
        当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
     2.就绪状态(Runnable)

        一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

        处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

    3.运行状态(Running)

        当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.

    4. 阻塞状态(Blocked)

        线程运行过程中,可能由于各种原因进入阻塞状态:
        1>线程通过调用sleep方法进入睡眠状态;
        2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
        3>线程试图得到一个锁,而该锁正被其他线程持有;
        4>线程在等待某个触发条件;
        ......           

        所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

    5. 死亡状态(Dead)

        有两个原因会导致线程死亡:
        1) run方法正常退出而自然死亡,
        2) 一个未捕获的异常终止了run方法而使线程猝死。
        为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

 

 

 

1、多线程有几种实现方法?同步有几种实现方法?

多线程有两种实现方法,分别是继承Thread类与实现Runnable接口

同步的实现方面有五种,分别是synchronized、wait与notify、sleep、suspend、join

synchronized: 一直持有锁,直至执行结束

wait():使一个线程处于等待状态,并且释放所持有的对象的lock,需捕获异常。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,需捕获异常,不释放锁。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

2、启动一个线程是用run()还是start()? 

启动一个线程是调用start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。

3、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

如果其它方法中使用当前对象作为锁对象,则不能;

如果其它方法中没有使用当前对象作为锁对象,则能。

4、线程的基本概念、线程的基本状态以及状态之间的关系

在多任务操作系统中,为了提高CPU的利用率,可以使用多进程编程。但对进程通信比较困难,进程间数据不能共享,因此可以使用多线程编程。一个进程至少包含一个主入口线程。

 

单个CPU,在同一时间只能处理一个线程的数据,但是操作系统的任务调度非常快,人眼无法识别,感觉上是多个线程同时执行。有的线程可以已经用完CPU,正在作磁盘操作,此时并不使用CPU,可以让出CPU资源给其它线程使用,提高效率。

 

 

5、sleep()和 wait()有什么区别?

(网上的答案:sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。)

 

sleep就是正在执行的线程主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,cpu才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notfiy方法后增加一个等待和一些代码,看看效果),调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。对于wait的讲解一定要配合例子代码来说明,

- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁; 
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常; 
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关; 
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

才显得自己真明白。

 

线程池

一简介

线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的。在jdk1.5之后这一情况有了很大的改观。Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。

二:线程池

线程池的作用:

线程池作用就是限制系统中执行线程的数量。
     根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要用线程池:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

比较重要的几个类:

ExecutorService

真正的线程池接口。

ScheduledExecutorService

能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。

ThreadPoolExecutor

ExecutorService的默认实现。

ScheduledThreadPoolExecutor

继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

1. newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2.newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3. newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4.newScheduledThreadPool

 

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

实例

1、newSingleThreadExecutor

 

MyThread.java

Java代码  

public class MyThread extends Thread {  

    @Override  

    public void run() {  

        System.out.println(Thread.currentThread().getName() + "正在执行。。。");  

    }  

}  

 TestSingleThreadExecutor.java

Java代码  

public class TestSingleThreadExecutor {  

    public static void main(String[] args) {  

        //创建一个可重用固定线程数的线程池  

        ExecutorService pool = Executors. newSingleThreadExecutor();  

        //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口  

        Thread t1 = new MyThread();  

        Thread t2 = new MyThread();  

        Thread t3 = new MyThread();  

        Thread t4 = new MyThread();  

        Thread t5 = new MyThread();  

        //将线程放入池中进行执行  

        pool.execute(t1);  

        pool.execute(t2);  

        pool.execute(t3);  

        pool.execute(t4);  

        pool.execute(t5);  

        //关闭线程池  

        pool.shutdown();  

    }  

}  

 输出结果(最多一个执行,执行完再执行另一个)

pool-1-thread-1正在执行。。。

pool-1-thread-1正在执行。。。

pool-1-thread-1正在执行。。。

pool-1-thread-1正在执行。。。


pool-1-thread-1正在执行。。。

2、newFixedThreadPool

 

TestFixedThreadPool.Java

Java代码  

public class TestFixedThreadPool {  

    public static void main(String[] args) {  

        //创建一个可重用固定线程数的线程池  

        ExecutorService pool = Executors.newFixedThreadPool(2);  

        //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口  

        Thread t1 = new MyThread();  

        Thread t2 = new MyThread();  

        Thread t3 = new MyThread();  

        Thread t4 = new MyThread();  

        Thread t5 = new MyThread();  

        //将线程放入池中进行执行  

        pool.execute(t1);  

        pool.execute(t2);  

        pool.execute(t3);  

        pool.execute(t4);  

        pool.execute(t5);  

        //关闭线程池  

        pool.shutdown();  

    }  

}  

 输出结果(最多两个一起执行,执行完一个再启一个。)

pool-1-thread-1正在执行。。。

pool-1-thread-2正在执行。。。

pool-1-thread-1正在执行。。。

pool-1-thread-2正在执行。。。

 

pool-1-thread-1正在执行。。。

3、newCachedThreadPool

TestCachedThreadPool.java

Java代码  

public class TestCachedThreadPool {  

    public static void main(String[] args) {  

        //创建一个可重用固定线程数的线程池  

        ExecutorService pool = Executors.newCachedThreadPool();  

        //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口  

        Thread t1 = new MyThread();  

        Thread t2 = new MyThread();  

        Thread t3 = new MyThread();  

        Thread t4 = new MyThread();  

        Thread t5 = new MyThread();  

        //将线程放入池中进行执行  

        pool.execute(t1);  

        pool.execute(t2);  

        pool.execute(t3);  

        pool.execute(t4);  

        pool.execute(t5);  

        //关闭线程池  

        pool.shutdown();  

    }  

}  

 输出结果:

pool-1-thread-2正在执行。。。

pool-1-thread-4正在执行。。。

pool-1-thread-3正在执行。。。

pool-1-thread-1正在执行。。。

pool-1-thread-5正在执行。。。

4、newScheduledThreadPool

TestScheduledThreadPoolExecutor.java

Java代码  

public class TestScheduledThreadPoolExecutor {  

    public static void main(String[] args) {  

        ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);  

        exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间就触发异常  

                      @Override  

                      publicvoid run() {  

                           //throw new RuntimeException();  

                           System.out.println("================");  

                      }  

                  }, 1000, 5000, TimeUnit.MILLISECONDS);  

        exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间打印系统时间,证明两者是互不影响的  

                      @Override  

                      publicvoid run() {  

                           System.out.println(System.nanoTime());  

                      }  

                  }, 1000, 2000, TimeUnit.MILLISECONDS);  

    }  

}  

 输出结果

================

8384644549516

8386643829034

8388643830710

================

8390643851383

8392643879319

 

8400643939383

三:ThreadPoolExecutor详解

ThreadPoolExecutor的完整构造方法的签名是:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
 TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, 

RejectedExecutionHandler handler) .

corePoolSize - 池中所保存的线程数,包括空闲线程。

maximumPoolSize-池中允许的最大线程数。

keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

unit - keepAliveTime 参数的时间单位。

workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。

threadFactory - 执行程序创建新线程时使用的工厂。

handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

ThreadPoolExecutor是Executors类的底层实现。

在JDK帮助文档中,有如此一段话:

“强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程)

它们均为大多数使用场景预定义了设置。”

下面介绍一下几个类的源码:

ExecutorService  newFixedThreadPool (int nThreads):固定大小线程池。

可以看到,corePoolSize和maximumPoolSize的大小是一样的(实际上,后面会介绍,如果使用无界queue的话maximumPoolSize参数是没有意义的),keepAliveTime和unit的设值表名什么?-就是该实现不想keep alive!最后的BlockingQueue选择了LinkedBlockingQueue,该queue有一个特点,他是无界的。

1.     public static ExecutorService newFixedThreadPool(int nThreads) {   

2.             return new ThreadPoolExecutor(nThreads, nThreads,   

3.                                           0L, TimeUnit.MILLISECONDS,   

4.                                           new LinkedBlockingQueue<Runnable>());   

5.         }

ExecutorService  newSingleThreadExecutor():单线程

1.     public static ExecutorService newSingleThreadExecutor() {   

2.             return new FinalizableDelegatedExecutorService   

3.                 (new ThreadPoolExecutor(1, 1,   

4.                                         0L, TimeUnit.MILLISECONDS,   

5.                                         new LinkedBlockingQueue<Runnable>()));   

6.         }

ExecutorService newCachedThreadPool():无界线程池,可以进行自动线程回收

这个实现就有意思了。首先是无界的线程池,所以我们可以发现maximumPoolSize为big big。其次BlockingQueue的选择上使用SynchronousQueue。可能对于该BlockingQueue有些陌生,简单说:该QUEUE中,每个插入操作必须等待另一个线程的对应移除操作。

1.     public static ExecutorService newCachedThreadPool() {   

2.             return new ThreadPoolExecutor(0, Integer.MAX_VALUE,   

3.                                           60L, TimeUnit.SECONDS,   

4.                                           new SynchronousQueue<Runnable>());   

    }

先从BlockingQueue<Runnable> workQueue这个入参开始说起。在JDK中,其实已经说得很清楚了,一共有三种类型的queue。

所有BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)

如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。

如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

queue上的三种类型。

 

排队有三种通用策略:

直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。  

BlockingQueue的选择。

例子一:使用直接提交策略,也即SynchronousQueue。

首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。在这里不是核心线程便是新创建的线程,但是我们试想一样下,下面的场景。

我们使用一下参数构造ThreadPoolExecutor:

1.     new ThreadPoolExecutor(   

2.                 2, 3, 30, TimeUnit.SECONDS,    

3.                 new  SynchronousQueue<Runnable>(),    

4.                 new RecorderThreadFactory("CookieRecorderPool"),    

            new ThreadPoolExecutor.CallerRunsPolicy());  

new ThreadPoolExecutor(

  2, 3, 30, TimeUnit.SECONDS,

  new SynchronousQueue<Runnable>(),

  new RecorderThreadFactory("CookieRecorderPool"),

  new ThreadPoolExecutor.CallerRunsPolicy());

 当核心线程已经有2个正在运行.

此时继续来了一个任务(A),根据前面介绍的“如果运行的线程等于或多于 corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程。”,所以A被添加到queue中。

又来了一个任务(B),且核心2个线程还没有忙完,OK,接下来首先尝试1中描述,但是由于使用的SynchronousQueue,所以一定无法加入进去。

此时便满足了上面提到的“如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。”,所以必然会新建一个线程来运行这个任务。

暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第一个添加入queue中,后一个呢?queue中无法插入,而线程数达到了maximumPoolSize,所以只好执行异常策略了。

所以在使用SynchronousQueue通常要求maximumPoolSize是无界的,这样就可以避免上述情况发生(如果希望限制就直接使用有界队列)。对于使用SynchronousQueue的作用jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。

什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中。

例子二:使用无界队列策略,即LinkedBlockingQueue

这个就拿newFixedThreadPool来说,根据前文提到的规则:

如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。那么当任务继续增加,会发生什么呢?

如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢?

如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,不一会儿就爆了。

例子三:有界队列,使用ArrayBlockingQueue

这个是最为复杂的使用,所以JDK不推荐使用也有些道理。与上面的相比,最大的特点便是可以防止资源耗尽的情况发生。

举例来说,请看如下构造方法:

     new ThreadPoolExecutor(   

                2, 4, 30, TimeUnit.SECONDS,    

                new ArrayBlockingQueue<Runnable>(2),    

                new RecorderThreadFactory("CookieRecorderPool"),    

                new ThreadPoolExecutor.CallerRunsPolicy());  

new ThreadPoolExecutor(

    2, 4, 30, TimeUnit.SECONDS,

    new ArrayBlockingQueue<Runnable>(2),

    new RecorderThreadFactory("CookieRecorderPool"),

    new ThreadPoolExecutor.CallerRunsPolicy());

假设,所有的任务都永远无法执行完。

对于首先来的A,B来说直接运行,接下来,如果来了C,D,他们会被放到queue中,如果接下来再来E,F,则增加线程运行E,F。但是如果再来任务,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。

keepAliveTime

jdk中的解释是:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

有点拗口,其实这个不难理解,在使用了“池”的应用中,大多都有类似的参数需要配置。比如数据库连接池,DBCP中的maxIdle,minIdle参数。

什么意思?接着上面的解释,后来向老板派来的工人始终是“借来的”,俗话说“有借就有还”,但这里的问题就是什么时候还了,如果借来的工人刚完成一个任务就还回去,后来发现任务还有,那岂不是又要去借?这一来一往,老板肯定头也大死了。

 

合理的策略:既然借了,那就多借一会儿。直到“某一段”时间后,发现再也用不到这些工人时,便可以还回去了。这里的某一段时间便是keepAliveTime的含义,TimeUnit为keepAliveTime值的度量。

 

RejectedExecutionHandler

另一种情况便是,即使向老板借了工人,但是任务还是继续过来,还是忙不过来,这时整个队伍只好拒绝接受了。

RejectedExecutionHandler接口提供了对于拒绝任务的处理的自定方法的机会。在ThreadPoolExecutor中已经默认包含了4中策略,因为源码非常简单,这里直接贴出来。

CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {   

                 if (!e.isShutdown()) {   

                     r.run();   

               }   

            }  

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

           if (!e.isShutdown()) {

               r.run();

           }

       }

这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。

AbortPolicy:处理程序遭到拒绝将抛出运行时RejectedExecutionException

     public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {   

                throw new RejectedExecutionException();   

            }  

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

           throw new RejectedExecutionException();

       }

 这种策略直接抛出异常,丢弃任务。

DiscardPolicy:不能执行的任务将被删除 

 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {   

          }  

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

       }

 这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。

DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程) 

  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {   

               if (!e.isShutdown()) {   

                    e.getQueue().poll();   

                    e.execute(r);   

                 }   

        }  

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

           if (!e.isShutdown()) {

               e.getQueue().poll();

               e.execute(r);

           }

       }

该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略需要适当小心。

设想:如果其他线程都还在运行,那么新来任务踢掉旧任务,缓存在queue中,再来一个任务又会踢掉queue中最老任务。

总结:

keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。

反之,如果核心数较小,有界BlockingQueue数值又较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。

public static ExecutorService newFixedThreadPool(int nThreads) {

       return new ThreadPoolExecutor(nThreads, nThreads,

                                     0L, TimeUnit.MILLISECONDS,

                                     new LinkedBlockingQueue<Runnable>());



   }

7、 解析xml的几种技术

        1.dom4j

        2.sax

        3.jaxb

        4.jdom

        5.dom

1.dom4j        

   dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。dom4j是一个非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。

 2.sax

        SAX(simple API for XML)是一种XML解析的替代方法。相比于DOM,SAX是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。而且相比于DOM,SAX可以在解析文档的任意时刻停止解析,但任何事物都有其相反的一面,对于SAX来说就是操作复杂。

  3.jaxb

         JAXB(Java Architecture for XML Binding) 是一个业界的标准,是一项可以根据XML Schema产生Java类的技术。该过程中,JAXB也提供了将XML实例文档反向生成Java对象树的方法,并能将Java对象树的内容重新写到XML实例文档。从另一方面来讲,JAXB提供了快速而简便的方法将XML模式绑定到Java表示,从而使得Java开发者在Java应用程序中能方便地结合XML数据和处理函数。

 

2、dom4j 与 sax 之间的对比:【注:必须掌握!】

     dom4j不适合大文件的解析,因为它是一下子将文件加载到内存中,所以有可能出现内存溢出,

  sax是基于事件来对xml进行解析的,所以他可以解析大文件的xml

  也正是因为如此,所以dom4j可以对xml进行灵活的增删改查和导航,而sax没有这么强的灵活性

  所以sax经常是用来解析大型xml文件,而要对xml文件进行一些灵活(crud)操作就用dom4j

 

 

优化

sq优化

  1、SELECT子句中避免使用 *, 尽量应该根据业务需求按字段进行查询

  2、尽量多使用COMMIT如对大数据量的分段批量提交释放了资源,减轻了服务器压力

  3、在写sql语句的话,尽量保持每次查询的sql语句字段用大写,因为oracle总是先解析      sql语句,把小写的字母转换成大写的再执行

  4、用UNION-ALL 替换UNION,因为UNION-ALL不会过滤重复数据,所执行效率        要快于UNION,并且UNION可以自动排序,而UNION-ALL不会

  5、避免在索引列上使用计算和函数,这样索引就不能使用  

Sql优化精简版:

1.(重点)(必须说) SELECT语句中避免使用 *,

                 尽量应该根据业务需求按字段进行查询

举例:如果表中有个字段用的是clob或者是blob这种大数据字段的话,

     他们的查询应该根据业务需要来进行指定字段的查询,切记勿直接用*

2.(重点) 删除重复记录(oracle):

最高效的删除重复记录方法 ( 因为使用了ROWID)例子:

DELETE  FROM  EMP E  WHERE  E.ROWID > (SELECT MIN(X.ROWID)

FROM  EMP X  WHERE  X.EMP_NO = E.EMP_NO);

3. 用>=替换>

    如一个表有100万记录,一个数值型字段A,

      A=0时,有30万条;

      A=1时,有30万条;

      A=2时,有39万条;

      A=3时,有1万记录。

      那么执行 A>2 与 A>=3 的效果就有很大的区别了,因为 A>2 时,

      ORACLE会先找出为2的记录索引再进行比较,

      而A>=3时ORACLE则直接找到=3的记录索引。

4.(重点)尽量多使用COMMIT

如对大数据量的分段批量提交

5. (重点)用NOT EXISTS 或(外连接+判断为空)方案 替换 NOT IN操作符   

    此操作是强列推荐不使用的,因为它不能应用表的索引。

    推荐方案:用NOT EXISTS 或(外连接+判断为空)方案代替

6.(重点 必须说)LIKE操作符(大数据的全文检索使用luncene)(solr)

    因为使用like不当,会导致性能问题,原因是like在左右两边都有

    %的时候,不会使用索引。       

    如LIKE '%5400%' 这种查询不会引用索引,

    而LIKE 'X5400%' 则会引用范围索引。

    一个实际例子:

    查询营业编号 YY_BH LIKE '%5400%' 这个条件会产生全表扫描,

    如果改成         YY_BH LIKE 'X5400%' OR YY_BH LIKE 'B5400%'

    则会利用    YY_BH  的索引进行两个范围的查询,性能肯定大大提高。

7.(重点,必须说)避免在索引列上使用计算和函数,这样索引就不能使用

   举例:

低效:

SELECT … FROM  DEPT  WHERE SAL * 12 > 25000;

高效:

SELECT … FROM DEPT WHERE SAL > 25000/12;

8.(重点 必须说)用UNION-ALL 替换UNION,

因为UNION-ALL不会过滤重复数据而且不会自动排序,

所执行效率要快于UNION。

9. (优化,重点,3个方面 a.缓存 b.分段批量 c.存储过程)减少访问数据库的次数

举例:如果批量删除多条数据,可以用  delete  from tableName where id

                                   in (1,2,3)

     而不要用多条delete语句进行删除

10.(重点 必须说)用TRUNCATE替代DELETE

TRUNCATE不记录日志,DELETE记录日志,所以TRUNCATE要快于DELETE

但是一旦用TRUNCATE进行删除就不能进行恢复,TRUNCATE是删除整张表的数据

不能加where条件。

==================================================================

mysql,sqlserver中如果

id为自增类型,那么如果用TRUNCATE删除,则id字段再插入数据时从1开始,

如果delete删除的话,则从删除之前的id的值继续增长。

防sql注入

       针对防sql注入,我们通常是这样做的:

           首先在前台页面对用户输入信息进行js验证,对一些特殊字符进行屏蔽,

   比如:or ,单引号,--,= ,还有就是限制用户名输入的长度,我们一般

           将其限制在6---13位。另外,对于用户的敏感信息我们进行Md5加密,还有

   ,为了增加用户体验度和用户友好度,为了不使用户看到一些详细的异常信息

   我们会进行错误信息页面的定制,像404,500错误。另一个我层面讲,这样做

   也是为了保护我们的一些重要信息。此外,我们会给特定的人分配定定的权限

   ,而不是给其分配管理员权限!

 sql注入

 

所谓SQL注入,就是通过一些含有特殊字符的sql语句发送到服务器欺骗服务器并进行攻击。(特殊字符:or, 单引号,--,空格)

 Sql注入的防护

1.永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式(js正则或者java后台正则),或限制长度;对单引号和双"-"进行转换等。

2.永远不要使用动态拼装sql,使用参数化的sql。(永远不要使用+号拼接sql字符串,而是使用?传参的方式进行)

3.不要给用户太高的权限而根据需求进行赋权

4.对敏感信息进行加密 如md5(单向加密不可逆转)

5.自定义错误页面。目的是为了不把我们的程序的bug暴露在别有用心的人的面前。而去不会让用户看到报错的页面,也提高了用户的体验度。

 

2.SQL注入防范

使用参数化的过滤性语句
  要防御SQL注入,用户的输入就绝对不能直接被嵌入到SQL语句中。恰恰相反,用户的输入必须进行过滤,或者使用参数化的语句。参数化的语句使用参数而不是将用户输入嵌入到语句中。在多数情况中,SQL语句就得以修正。然后,用户输入就被限于一个参数。

输入验证


  检查用户输入的合法性,确信输入的内容只包含合法的数据。数据检查应当在客户端服务器端(java代码)都执行之所以要执行服务器端验证,是为了弥补客户端验证机制脆弱的安全性
  在客户端,攻击者完全有可能获得网页的源代码,修改验证合法性的脚本(或者直接删除脚本),然后将非法内容通过修改后的表单提交给服务器。因此,要保证验证操作确实已经执行,唯一的办法就是在服务器端也执行验证。你可以使用许多内建的验证对象,例如Regular Expression Validator,它们能够自动生成验证用的客户端脚本,当然你也可以插入服务器端的方法调用。如果找不到现成的验证对象,你可以通过Custom Validator自己创建一个。

错误消息处理


  防范SQL注入,还要避免出现一些详细的错误消息,因为黑客们可以利用这些消息。要使用一种标准的输入确认机制来验证所有的输入数据的长度、类型、语句、企业规则等。

加密处理

将用户登录名称、密码等数据加密保存。加密用户输入的数据,然后再将它与数据库中保存的数据比较,这相当于对用户输入的数据进行了“消毒”处理,用户输入的数据不再对数据库有任何特殊的意义,从而也就防止了攻击者注入SQL命令。

存储过程来执行所有的查询


SQL参数的传递方式将防止攻击者利用单引号和连字符实施攻击。此外,它还使得数据库权限可以限制到只允许特定的存储过程执行,所有的用户输入必须遵从被调用的存储过程的安全上下文,这样就很难再发生注入式攻击了。

 

3.tomca优化:

增大内存(堆,持久代)并开启server模式

我在做XXX项目时,用到了poi导入和导出数据,由于公司的业务比较繁多,数据量很大,测试时报内存溢出,经过我的分析再结合上网查阅资料,发现可能是tomcat内存不足,需要增大,修改配置文件后测试不再报错.

tomcat增大内存的方式通过修改tomcat配置文件

window下, 在bin/catalina.bat文件中最前面添加:

set JAVA_OPTS=-XX:PermSize=64M -XX:MaxPermSize=128m –Xms1024m -Xmx1024m

linux下,在catalina.sh最前面增加:

JAVA_OPTS="-XX:PermSize=64M -XX:MaxPermSize=128m –Xms1024m -Xmx1024m "

-client –service

当我们在cmd中运行-java时,黑窗口会出现-client -service这两参数.其作用是设置虚拟机运行模式;client模式启动比较快,但运行时性能和内存管理效率不如server模式,通常用于客户端应用程序。server模式启动比client慢,但可获得更高的运行性能。Windows默认为client,如果要使用server模式,就需要在启动虚拟机时加-server参数,以获得更高性能,对服务器端应用,推荐采用server模式,尤其是多个CPU的系统。在Linux,Solaris上,默认值为server模式.

JDK版本

影响虚拟机还有JDK的版本,JDK分为32位,64位两种版本,32位装在32位系统,64位系统可以装32位和64位JDK.64位JDK性能优于32位JDK.

测试的命令 java -xmx数值m –version  报错配置大小失败,反之成功

增加Tomcat最大连接数

使用场景

我在做完一个XXX项目后,测试时发现并发数量增加到一定程度就会很卡,于是我想到了是不是tomcat最大连接数设置有限制.果不其然,配置文件中最大值才500,于是我更改了最大连接数,根据业务我修改了连接数为2000,完美的解决了这个问题;

修改方法在conf/service.xml中默认值

<Connector port="8080" maxHttpHeaderSize="8192" maxThreads="1500"

   minSpareThreads="30" maxSpareThreads="75" enableLookups="false"

   redirectPort="8443" acceptCount="100" connectionTimeout="20000"

   disableUploadTimeout="true" />

,修改maxthreads的值即可

tomcat进行gzip压缩从而降低网络传输量

tomcat 压缩设置tomcat压缩gzip启用

 

HTTP 压缩可以大大提高浏览网站的速度,它的原理是,

在客户端请求服务器对应资源后,从服务器端将资源文件压缩,

再输出到客户端,由客户端的浏览器负责解压缩并浏览。

相对于普通的浏览过程HTML ,CSS,Javascript , Text ,

它可以节省60%左右的流量。更为重要的是,它可以对动态生成的,

包括CGI、PHP , JSP , ASP , Servlet,SHTML等输出的网页也能进行压缩,

压缩效率也很高。

启用tomcat 的gzip压缩

要使用gzip压缩功能,你需要在Connector节点中加上如下属性

 

 

compression="on" 打开压缩功能

compressionMinSize="50" 启用压缩的输出内容大小,默认为2KB

noCompressionUserAgents="gozilla, traviata" 对于以下的浏览器,不启用压缩

compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain" 哪些资源类型需要压缩

<Connector port="80" protocol="HTTP/1.1"   

           connectionTimeout="20000"   

           redirectPort="8443" executor="tomcatThreadPool" URIEncoding="utf-8"   

           compression="on"   

           compressionMinSize="50" noCompressionUserAgents="gozilla, traviata"   

          compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain" />

 

设计模式和反射

工厂模式:

工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A(). 工厂模式也是用来创建实例对象的,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。

类Sample为例,要创建Sample的实例对象:

Sample sample=new Sample();
可是,实际情况是,通常我们都要在创建sample实例时做点初始化的工作,比如赋值 查询数据库等

首先,我们想到的是,可以使用Sample的构造函数,这样生成实例就写成:

Sample sample=new Sample(参数);

但是,如果创建sample实例时所做的初始化工作不是象赋值这样简单的事,可能是很长一段代码,如果也写入构造函数中,那你的代码很难看了

初始化工作如果是很长一段代码,说明要做的工作很多,将很多工作装入一个方法中,相当于将很多鸡蛋放在一个篮子里,是很危险的,这也是有背于Java面向对象的原则,面向对象的封装(Encapsulation)和分派(Delegation)告诉我们,尽量将长的代码分派“切割”成每段,将每段再“封装”起来(减少段和段之间偶合联系性),这样,就会将风险分散,以后如果需要修改,只要更改每段,不会再发生牵一动百的事情。

我们需要将创建实例的工作与使用实例的工作分开, 也就是说,让创建实例所需要的大量初始化工作从Sample的构造函数中分离出去。

你想如果有多个类似的类,我们就需要实例化出来多个类。这样代码管理起来就太复杂了。
这个时候你就可以采用工厂方法来封装这个问题。
不能再用上面简单new Sample(参数)。还有,如果Sample有个继承如MySample, 按照面向接口编程,我们需要将Sample抽象成一个接口.现在Sample是接口,有两个子类MySample 和HisSample

Sample mysample=new MySample();
Sample hissample=new HisSample();
采用工厂封装:
 

public class Factory{

  public static Sample creator(int which){

  //getClass 产生Sample 一般可使用动态类装载装入类。
  if (which==1)
    return new SampleA();
  else if (which==2)
    return new SampleB();

  }

}



那么在你的程序中,如果要实例化Sample时.就使用

Sample sampleA=Factory.creator(1);

举个更实际的例子,比如你写了个应用,里面用到了数据库的封装,你的应用可以今后需要在不同的数据库环境下运行,可能是oracle,db2,sql server等,那么连接数据库的代码是不一样的,你用传统的方法,就不得不进行代码修改来适应不同的环境,非常麻烦,但是如果你采用工厂类的话,将各种可能的数据库连接全部实现在工厂类里面,通过你配置文件的修改来达到连接的是不同的数据库,那么你今后做迁移的时候代码就不用进行修改了。

我通常都是用xml的配置文件配置许多类型的数据库连接,非常的方便。PS:工厂模式在这方面的使用较多。

工厂模式分为:

简单工厂模式(被称为静态模式)

工厂方法模式

抽象工厂模式

 

面向对象原则:

Ocp基本

工厂模式核心本质:实例化对象,用工厂方法代替new;

 

 

 

単例模式

显然单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向其他类提供这个实例。

懒汉模式

只有用到的时候对象才会初始化,获取对象会比较慢,但是加载类的时候比较快

恶汉模式

恶汉模式是无论用,或不用都会先初始化,在运行时获取对象速度比较快,但是在加载类的时候慢;

/*

 *饿汉模式

 */

public class Single {

// 私有的的构造器

private  Single(){



}

// 提前实例化好

// final可以加可以不加,加上更眼睛

private static final Single single = new Single();

// 共有的静态方法

public static Single getSingle(){

return single;

}



}



/*

 * 懒汉模式

 */

public class Single2 {

// 私有构造器

private Single2(){



}

// 对象为空

private static Single2 single=null;

// 共有的静态方法

public static Single2 getsingel(){

// 判断是否为空,本质是是否获得过这个对象,如果之前调用过,就不执行。如果没有调用过,说明为空,执行

if(single == null){

// 实例化

single = new Single2();

}

return single;

}



}

/*

 * 双重锁模式

 */

public class Single3 {

// 私有的构造器

private Single3(){



};

// 初始化对象为空

private static Single3 single = null;



public static Single3 getsingle(){

// public static synchronized Single3 getsingle(){ 也可以在方法上加锁,但是浪费性能,所有的对象为null的不为null的都必须等;



if(single == null){//先判断是否为空在加锁

synchronized(Single3.class){//static修饰的锁,对象级别是class字节码

if(single == null){

single = new Single3();

}

}

}

return single;

}



}

4.原型模式

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。

重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。

        原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。

原型模式的优点及适用场景

       使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

       使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。

       因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。

原型模式的注意事项

使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。

深拷贝与浅拷贝。

Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。

1.浅复制与深复制概念
⑴浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不

复制它所引用的对象。

⑵深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原

有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

java反射:

反射就是可以将一个程序(类)在运行的时候获得该程序(类)的信息的机制,也就是获得在编译期不可能获得的类的信息,因为这些信息是保存在Class对象中的,而这个Class对象是在程序运行时动态加载的 
它 就是可以在程序运行的时候动态装载类,查看类的信息,生成对象,或操作生成对象。类在运行的时候,可以得到该类的信息,并且 可以动态的修改这些信息。class对象是在运行的时候产生的,通过class对象操作类的信息是在运行时进行的,当运行 程序的时候,类加载器会加载真正需要的类,什么是真正需要的呢?就是该类真正起作用,如:有该类的对象实例,或该类调用了静态方法属性等 

实现反射的三种凡是

Class.forName(“全限类名”).class

对象.class

Class <?> class = 类名.class

通过class对象获取类的方法、参数、属性

事务

什么是事务控制?


答:事务控制就是将一系列操作当成一个不可拆分的逻辑单元,保证这些操作要么都成功,要么都失败。在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。
事务是恢复和并发控制的基本单位。


事务应该具有4个属性:原子性、一致性、隔离性、持续性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务有两种操作:提交,回滚

14.jvm

1.Jvm的内存结构


1、程序计数器

      这是个什么鬼呢?我们都知道,CPU的计算时间是以分片的方式给到每个线程的,换句话说,所谓的并行其本质就是串行。比如线程A执行到了一部分,CPU将控制权给了线程B,那么线程A重新得到CPU的资源时,如何恢复工作呢?这个程序计数器就来帮助线程A找到其中间状态,从而恢复到正确的执行位置。程序计数器所占内存是线程私有的,同时也是Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。    

 

2、JAVA虚拟机栈

 它也是线程私有的,它所占有的内存空间也就是我们平时所说的“栈(stack)内存”。并且和线程的生命周期相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame ①)用于存储局部变量表(基本数据类型,对象的引用和returnAddress类型)、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

3、JAVA堆

      JAVA堆一般是JVM管理的内存中最大的一块,JAVA堆在主内存中,是被所有线程共享的一块内存区域,其随着JVM的创建而创建,堆内存的唯一目的是存放对象实例。同时JAVA堆也是GC管理的主要区域

 

如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java 堆中还可以细分为:新生代和老年代;再细致一点的有Eden 空间、From Survivor 空间、To Survivor 空间等。

如果从内存分配的角度看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。

不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。

 

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。


4、本地方法栈

 

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。

5、方法区

 

方法区也是各线程共享的一个内存区域。主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

 

虽然Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java 堆区分开来。

 

Java 虚拟机规范对这个区域的限制非常宽松,除了和Java 堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。在Sun 公司的BUG 列表中,曾出现过的若干个严重的BUG 就是由于低版本的HotSpot 虚拟机对此区域未完全回收而导致内存泄漏。


根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。

6、常量池

        Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表(constant_pool table),用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放(JDK1.7开始,常量池已经被移到了堆内存中)。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的String.intern()方法)。

2.Jdk调优:

堆设置
-Xmx3550m:设置JVM最大堆内存 为3550M。 
-Xms3550m:设置JVM初始堆内存 为3550M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。 
-Xss128k: 设置每个线程的栈 大小。JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K。应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能 生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。 
-Xmn2g:设置堆内存年轻代 大小为2G。整个堆内存大小 = 年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。 
-XX:PermSize=256M:设置堆内存持久代 初始值为256M。(貌似是Eclipse等IDE的初始化参数) 
-XX:MaxNewSize=size:新生成的对象能占用内存的最大值。 
-XX:MaxPermSize=512M:设置持久代最大值为512M。 
-XX:NewRatio=4:设置堆内存年轻代(包括Eden和两个Survivor区)与堆内存年老代的比值(除去持久代) 。设置为4,则年轻代所占与年老代所占的比值为1:4。 
-XX:SurvivorRatio=4: 设置堆内存年轻代中Eden区与Survivor区大小的比值 。设置为4,则两个Survivor区(JVM堆内存年轻代中默认有2个Survivor区)与一个Eden区的比值为2:4,一个Survivor区占 整个年轻代的1/6。 
-XX:MaxTenuringThreshold=7:表示一个对象如果在救助空间(Survivor区)移动7次还没有被回收就放入年老代。 
如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代,对于年老代比较多的应用,这样做可以提高效率。 
如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代存活时间,增加对象在年轻代即被回收的概率。 
回收器选择
JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。

默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行智能判断。

串行收集器 
-XX:+UseSerialGC:设置串行收集器 
并行收集器(吞吐量优先) 
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。 
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。 
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。 
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间(单位毫秒),如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。 
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时间或者收集频率等。 
此参数建议使用并行收集器时,一直打开。 
并发收集器(响应时间优先) 
-XX:+UseParNewGC:设置年轻代为并发收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。 
CMS, 全称Concurrent Low Pause Collector,是jdk1.4后期版本开始引入的新gc算法,在jdk5和jdk6中得到了进一步改进,它的主要适合场景是对响应时间的重要性需求 大于对吞吐量的要求,能够承受垃圾回收线程和应用线程共享处理器资源,并且应用中存在比较多的长生命周期的对象的应用。CMS是用于对tenured generation的回收,也就是年老代的回收,目标是尽量减少应用的暂停时间,减少FullGC发生的几率,利用和应用程序线程并发的垃圾回收线程来 标记清除年老代。 
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了。所以,此时年轻代大小最好用-Xmn设置。 
-XX:CMSFullGCsBeforeCompaction=:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此参数设置运行次FullGC以后对内存空间进行压缩、整理。 
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除内存碎片。 
-XX:+CMSIncrementalMode:设置为增量收集模式。一般适用于单CPU情况。 
-XX:CMSInitiatingOccupancyFraction=70:表示年老代空间到70%时就开始执行CMS,确保年老代有足够的空间接纳来自年轻代的对象。 
注:如果使用 throughput collector 和 concurrent low pause collector 这两种垃圾收集器,需要适当的挺高内存大小,为多线程做准备。

其它
-XX:+ScavengeBeforeFullGC:新生代GC优先于Full GC执行。 
-XX:-DisableExplicitGC:禁止调用System.gc(),但JVM的gc仍然有效。 
-XX:+MaxFDLimit:最大化文件描述符的数量限制。 
-XX:+UseThreadPriorities:启用本地线程优先级API,即使 java.lang.Thread.setPriority() 生效,反之无效。 
-XX:SoftRefLRUPolicyMSPerMB=0:“软引用”的对象在最后一次被访问后能存活0毫秒(默认为1秒)。 
-XX:TargetSurvivorRatio=90:允许90%的Survivor空间被占用(默认为50%)。提高对于Survivor的使用率——超过就会尝试垃圾回收。 
辅助信息
-XX:-CITime:打印消耗在JIT编译的时间 
-XX:ErrorFile=./hs_err_pid.log:保存错误日志或者数据到指定文件中 
-XX:-ExtendedDTraceProbes:开启solaris特有的dtrace探针 
-XX:HeapDumpPath=./java_pid.hprof:指定导出堆信息时的路径或文件名 
-XX:-HeapDumpOnOutOfMemoryError:当首次遭遇内存溢出时导出此时堆中相关信息 
-XX:OnError=";":出现致命ERROR之后运行自定义命令 
-XX:OnOutOfMemoryError=";":当首次遭遇内存溢出时执行自定义命令 
-XX:-PrintClassHistogram:遇到Ctrl-Break后打印类实例的柱状信息,与jmap -histo功能相同 
-XX:-PrintConcurrentLocks:遇到Ctrl-Break后打印并发锁的相关信息,与jstack -l功能相同 
-XX:-PrintCommandLineFlags:打印在命令行中出现过的标记 
-XX:-PrintCompilation:当一个方法被编译时打印相关信息 
-XX:-PrintGC:每次GC时打印相关信息 
-XX:-PrintGC Details:每次GC时打印详细信息 
-XX:-PrintGCTimeStamps:打印每次GC的时间戳 
-XX:-TraceClassLoading:跟踪类的加载信息 
-XX:-TraceClassLoadingPreorder:跟踪被引用到的所有类的加载信息 
-XX:-TraceClassResolution:跟踪常量池 
-XX:-TraceClassUnloading:跟踪类的卸载信息 
-XX:-TraceLoaderConstraints:跟踪类加载器约束的相关信息 
JVM服务调优实战 
服务器:8 cup, 8G mem

e.g. 
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0

调优方案:

-Xmx5g:设置JVM最大可用内存为5G。 
-Xms5g:设置JVM初始内存为5G。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。 
-Xmn2g:设置年轻代大小为2G。整个堆内存大小 = 年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。 
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。 
-XX:ParallelGCThreads=8:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。 
-XX:SurvivorRatio=6:设置年轻代中Eden区与Survivor区的大小比值。根据经验设置为6,则两个Survivor区与一个Eden区的比值为2:6,一个Survivor区占整个年轻代的1/8。 
-XX:MaxTenuringThreshold=30: 设置垃圾最大年龄(次数)。如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值 设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。设置为30表示 一个对象如果在Survivor空间移动30次还没有被回收就放入年老代。 
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试配置这个参数以后,参数-XX:NewRatio=4就失效了,所以,此时年轻代大小最好用-Xmn设置,因此这个参数不建议使用。 
参考资料 - JVM堆内存的分代 
虚 拟机的堆内存共划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集器要收集的Java对象关系不大。所以,年轻代和年老代的划分才是对垃圾 收集影响比较大的。

年轻代 
所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。

大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当一个Survivor区满 时,此区的存活对象将被复制到另外一个Survivor区,当另一个Survivor区也满了的时候,从前一个Survivor区复制过来的并且此时还存 活的对象,将被复制“年老区(Tenured)”。

需要注意,两个Survivor区是对称的,没先后关系,所以同一个Survivor区中可能同时存在从Eden区复制过来对象,和从另一个 Survivor区复制过来的对象;而复制到年老区的只有从前一个Survivor区(相对的)过来的对象。而且,Survivor区总有一个是空的。特 殊的情况下,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

年老代 


在年轻代中经历了N(可配置)次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

持久代 


用于存放静态数据,如 Java Class, Method 等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些Class,例如 Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中动态增加的类型。持久代大小通过 -XX:MaxPermSize= 进行设置。

请解释一下jvm加载class文件的原理机制

Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的, 类装载器所做的工作实质是把类文件从硬盘读取到内存中,

.类装载方式,有两种 
    1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中, 
    2.显式装载, 通过class.forname()等方法,显式加载需要的类 
  隐式加载与显式加载的区别: 
    两者本质是一样?, 

  1. java类装载器 
        Java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并不是一个,而是三个,层次结构如下: 
          Bootstrap Loader  - 负责加载系统类 
                | 
              - - ExtClassLoader  - 负责加载扩展类 
                              | 
                          - - AppClassLoader  - 负责加载应用类 
            为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型

 

3.内存泄露和内存溢出

内存泄露 (memory leak),是指应用程序在申请内存后,

无法释放已经申请的内存空间.一次内存泄露危害可以忽略,

但如果任其发展最终会导致内存溢出(out of memory).

如读取文件后流要进行及时的关闭以及对数据库连接的释放。

 

内存溢出(out of memory)是指应用程序在申请内存时,

没有足够的内存空间供其使用。

如我们在项目中对于大批量数据的导入,采用分段批量提交的方式。

4.java类加载器 :

java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器(也叫应用类加载器)

类加载器是Java最强大的特征之一。但是开发者常常忘记类加载组件。类加载器是在运行时负责寻找和加载类文件的类。Java允许使用不同的类加载器,甚至自定义的类加载器。

Java 程序包含很多类文件,每一个都与单个Java类相对应,这些类文件不像静态C程序,一次性加载入内存,它们随时需要随时加载。这就是类加载器与众不同的地 方。它从源文件(通常是.class 或 .jar文件)获得不依赖平台的字节码,然后将它们加载到JVM内存空间,所以它们能被解释和执行。默认状态下,应用程序的每个类由 java.lang.ClassLoader加载。因为它可以被继承,所以可以自由地加强其功能。

使用自定义类加载器的原因

  默认的 java.lang.ClassLoader仅仅可以从加载本地文件系统的类。Java被设计成不论本地磁盘或网络都有足够的弹性加载类,并且可以在加载 之前处理特殊事物。例如:应用程序可以检查Web站点或FTP上插入类的更新版本并且自动校验数字签名确保执行可信任的代码。许多众所周知的软件都使用自 己的类加载器。

通常默认加载器是所谓的bootstrap类加载器;它负责加载诸如java.lang.Object等关键类和加 载其他rt.jar文件的运行时代码到内存。因为Java语言规范没有提供bootstrap类加载器的详细信息,不同的JVM可能有不同的类加载器。如 果看到网页上有applets在运行,则它使用的是自定义类加载器。嵌入到浏览器中的applet阅读器包含了可以访问远程服务器上站点的类加载器,它可 以通过HTTP加载原始字节码文件,并且在JVM中将它们转换成类。

  类加载器(除了bootstrap类加载器)有父类加载器,这些父类是基本加载器的加载器实例。最重要的一点是设置正确的父加载器。然后可以使用 类加载器的getParent()方法实现委派类请求(例如:自定义类加载器找不到使用专门方法的类时)。此时必须为将父加载器作为 java.lang.ClassLoader构造器的参数:

public class MyClassLoader extends ClassLoader

{ 
public MyClassLoader()

{

super(MyClassLoader.class.getClassLoader()); 
}

}

loadClass(String name)方法是ClassLoader的入口。名字参数是完全资格类名(FQCN),例如关于包类名。如果父加载器设置正确,当请求 MyClassLoader中的loadClass(String name)方法加载类,但又找不到需要加载的类时,则首先会询问父加载器。如果父加载器也找不到此类,则调用findClass(String name)方法。默认状态下findClass(String name)会抛出ClassNotFoundException例外,很多开发人员都很清楚这个例外。自定义类加载器的开发者都希望从 java.lang.ClassLoader继承时跳过这个方法。

findClass()方法的目标是为MyClassLoader容纳所有专门代码,此时不需要重复其他代码(例如当加载失败时调用系统 ClassLoader)。在此方法中,ClassLoader需要从原文件中获取字节码。一旦找到字节码则会调用defineClass()方法。 ClassLoader实例调用此方法是非常重要的。因此,如果两个ClassLoader实例定义了来自不同或相同原文件的字节码,则被定义的类也将区 别对待。

我们给出两个相似的类加载器MyClassLoader1 和 MyClassLoader2,它们都可以从相同的源文件找到MyCoolClass字节码。如果一个程序通过这两个加载器分别独立加载 MyCoolClass实例(coolClass1通过MyClassLoader1加载, coolClass2通过MyClassLoader2加载),MyCoolClass.class能够被独立定义。执行下面的代码:

MyCoolClass coolClass1 = (MyCoolClass)coolClass2;

  将得到一个ClassCastException例外。(开发者如果没有很好的理解类加载机制则经常碰到这样的情况。)因为它们是不同的加载器 所定义的,JVM将它们看成不同的类。虽然它们是相同类型的类并且从相同的源文件加载,但是变量coolClass1和coolClass2不兼容。

不论是否跳过findClass() 或 loadClass(),getSystemClassLoader()方法将以实际ClassLoader对象的形式直接访问系统 ClassLoader。也可以通过调用findSystemClass(String name)方法间接访问。getParent()方法允许获得父加载器。Listing A给出了可以运行的自定义类加载器示例。

 

Java基础

get和post请求的区别? 


答: 
①get请求用来从服务器上获得资源,而post是用来向服务器提交数据; 
②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?"连接,而各个变量之间使用"&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL; 
③get传输的数据要受到URL长度限制(1024字节);而post可以传输大量的数据,上传文件通常要使用post方式; 
④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post; 
⑤get使用MIME类型application/x-www-form-urlencoded的URL编码(也叫百分号编码)文本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是"%20"。

java基本数据类型的大小

Int long double  char

说说&和&&的区别。

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

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

 

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

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

 

备注:这道题先说两者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来表明自己理解透彻深入、实际经验丰富。

2、"=="和equals方法究竟有什么区别?

他们的区别主要存在在引用数据类型上

==为比较两侧的对象是否同一对象,是用内存地址来比较的

 

equals是方法,默认是用内存地址比较,重写后,主要是用来比较两侧的对象的值是否相同,和equals方法中的实现有关

==可以两侧都为null,但equals左侧的引用指向的对象不能空,不然有NullPointerException

 

除非需要比较两个引用指向的对象是同一对象,一般都使用equals方法进行比较。尤其是String之类的值对象,另外,常量尽量放在比较的左侧

3、Integer与int的区别

int是java提供的8种原始数据类型之一,意思整型,占用4字节。

Integer是java为int提供的封装类,是引用数据类型。

int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况。

例如,要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer

 

在JSP开发中,Integer的默认为null,所以用el表达式在文本框中显示时,值为空白字符串,而int默认的默认值为0,所以用el表达式在文本框中显示时,结果为0,所以,int不适合作为web层的表单数据的类型。

 

在Hibernate中,如果将OID定义为Integer类型,那么Hibernate就可以根据其值是否为null而判断一个对象是否是临时的,如果将OID定义为了int类型,还需要在hbm映射文件中设置其unsaved-value属性为0。

 

另外,Integer提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。

4、面向对象的特征有哪些方面

1. 封装,隐藏内部实现,只暴露公共行为

2. 继承,提高代码的重用性

3. 多态,体现现实生活中相似对象的差异性

4. 抽象,抽取现实世界中相似对象的共同点

5、abstract class和interface有什么区别?

含有abstract修饰符的class即为抽象类,abstract类不能创建的实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

下面比较一下两者的语法区别:

1. 抽象类可以有构造方法,接口中不能有构造方法。

2. 抽象类中可以有普通成员变量,接口中没有普通成员变量

3. 抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

4. 抽象类中的抽象方法的访问类型可以是public,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

5. 抽象类中可以包含静态方法,接口中不能包含静态方法

6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

7. 一个类可以实现多个接口,但只能继承一个抽象类。

下面接着再说说两者在应用上的区别:

接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约;

而抽象类在代码实现方面发挥作用,可以实现代码的重用。

 

6、什么是内部类?Static Nested Class和Inner Class的不同。

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

1. 静态内部类需要使用static修饰,而普通内部类不能使用static修饰

2. 静态内部类只能定义在和属性同级,普通内部类可以定义在除参数位置以外的任意位置

3. 静态内部类必需有名称,而普通内部类可以是匿名的

4. 静态内部类没有this引用,只此只能访问外部类的静态成员,而普通内部类可以访问外部类的全部成员

5. 静态内部类访问外部类的同名函数时,使用“外部类名.方法名”即可,而普通内部类需要使用“外部类名.this.外部方法”

6. 静态内部类可以定义静态方法,而普通内部类不能定义静态方法,但能定义简单数据类型的静态属性,不能定义引用类型的静态属性。

7、内部类可以引用它的包含类的成员吗?有没有什么限制?

1. 如果内部类为静态内部类,只能调用外部类的静态成员;如果有重名成员,需要用“外部类名.成员名”访问;不能调用外部类的对象成员。

2. 如果内部类为非静态内部类,则可以调用外部类的所有成员;如果有重名成员,需要使用“外部类名.this.成员名”

8、String是最基本的数据类型吗?

基本数据类型包括byte、int、char、long、float、double、boolean和short。

String是引用数据类型。

java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer/StringBuilder类

9、String s = new String("xyz");创建了几个String Object?二者之间有什么区别?

两个对象。一个是"xyz",为缓冲区对象。另一个是new出来的String对象。

这两个对象的值相同,但不是同一个对象。

补充,新建对象有几种方式?

1. 使用new关键字

2. 使用反射,调用newInstance

3. 使用clone方法

4. 使用序列化与反序列化

5. 动态代理(Proxy类和CGLIB)

10、String和StringBuffer的区别

这两个类都实现了CharSequence接口。

1. 类型不同,因为不是一个类,也没有继承关系,做参数时不能共用

2. String对象是不可变对象,不能修改值。而StringBuffer是可变对象,能修改值。

3. 拼接字符串时,String会产生新对象,而StringBuffer只是增加新字符,不产生新对象,因此效率高。

4. String覆盖了equals方法和hashCode方法,而StringBuffer没有覆盖equals方法和hashCode方法,所以,将StringBuffer对象存储进Java集合类中时会出现问题。

11、StringBuffer和StringBuilder的区别

相同之处,这两类都是可变长的字符串存储类,都实现了CharSequence接口

1. 类型不同,因为不是一个类,也没有继承关系,做参数时不能共用

2. StringBuffer为线程安全类,StringBuilder为线程非安全类

3. StringBuffer性能低,StringBuilder性能高,如果在局部优先使用StringBuilder

4. JDK在1.5之前,字符串相加使用StringBuffer对象,在1.5之后使用StringBuilder对象

 

12、final, finally, finalize的区别。

final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。

内部类要访问局部变量,局部变量必须定义成final类型

final int[] number = { 20 };

new Thread() {

@Override

public void run() {

for (int k = 0; k < 20; k++) {

number[0]++;

}

}

}.start();



Thread.sleep(10);

System.out.println(number[0]);

finally是异常处理语句结构的一部分,表示总是执行,用来释放资源。

 

finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用

13、Java中的异常处理机制的简单原理和应用。

异常是指java程序运行时(非编译)所发生的非正常情况或错误。

Java使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别封装到一个对象中,该对象中包含有异常的信息。

Java可以自定义异常类,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception。

Error表示应用程序本身无法克服和恢复的一种严重问题,程序只有退的份了,例如说内存溢出和线程死锁等系统问题。

Exception表示程序还能够克服和恢复的问题,其中又分为运行时异常和检查异常,运行时异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉。例如,数组越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);检查异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。

Java为运行时异常和检查异常提供了不同的解决方案,编译器强制检查异常必须try..catch处理或用throws声明继续抛给上层调用方法处理,所以检查异常也称为checked异常,而运行异常可以处理也可以不处理,所以编译器不强制用try..catch处理或用throws声明,所以运行异常也称为Runtime异常。

 

提示答题者:就按照三个级别去思考:虚拟机必须宕机的错误,程序可以死掉也可以不死掉的错误,程序不应该死掉的错误

14、Java语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?

Java语言如何进行异常处理见43题

throws为向上抛异常

throw程序出错时,手工抛出异常

try尝试执行,里面的语句可能出现异常,如出现异常需要处理

catch处理try中出现的异常

finally在try后执行清理操作,用于释放资源

在try中可以抛出异常

15、HTTP请求的GET与POST方式的区别

答:

1. URL地址长度不同, GET支持的字符少

2. GET的密码是明文,安全问题,容易受到黑客攻击

3. GET只传输文本,不支持文件传输

4. GET方式通常用来查询,不用来修改数据,是幂等操作,修改数据用POST

 

16、解释一下什么是servlet;

答: 通常Servlet特指HttpServlet,用来接受浏览器的访问请求,浏览器最常用的请求为GET和POST方式,还有其它五种,而HttpServlet分别有七个方法(PUT、DELETE、HEADER、TRACE、OPTION)处理这些类型的请求,另有一个是J2EE不支持的,是CONNECT。Servlet是J2EE规范中的重要成员,是构成WEB的重要组件

17、说一说Servlet的生命周期?

1. 加载Servlet类

2. 实例化

3. 初始化init

4. 处理请求 service à 进一步调用doGet/doPost方法

5. 销毁 destory

18、Servlet的基本架构

1. 定义一个Servlet类,继承HttpServlet抽象类

2. 在web.xml中定义一个servlet标签,配置类名和servlet名

3. 配置servlet处理的URL请求连接,可以用模糊匹配

4. 在J2EE生命周期中,一个Servlet只有一个实例

5. 一个Servlet可以为多个请求服务,每个请求在独立的线程中执行

19、Servlet API中forward()与redirect()的区别?

Forward: 服务器端内部跳转,URL地址不变,属于单次请求

Redirect: 服务器通知浏览器中转,URL地址发生改变,是两次跳转

 

Forward不能跨域跳转

Redirect可以跨域跳转

 

Forward在两个页面传值可以通过parameter,也可以通过attribute,能传递Java对象

Redirect在两个页面传值只能通过parameter,在URL中传参

20、什么情况下调用doGet()和doPost()?

Jsp页面中的FORM标签里的method属性为get时调用doGet(),为post时调用doPost()。

在地址栏进接输入URL回车,会调用doGet()方法

字节流和字符流的区别

Java命名规范有哪些

请说明jsp中动态include和静态include的区别

Java的序列化,如何实现

25Jquery的常见选择器有哪些

26、java的基本数据类型

数据类型 大小

byte(字节) 1(8位)

shot(短整型) 2(16位)

int(整型) 4(32位)

long(长整型) 8(32位)

float(浮点型) 4(32位)

double(双精度) 8(64位)

char(字符型) 2(16位)

boolean(布尔型) 1位

附加:

 String是基本数据类型吗?(String不是基本数据类型)

是下面比较一下两者的语法区别:
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然
eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
7. 一个类可以实现多个接口,但只能继承一个抽象类。
 下面接着再说说两者在应用上的区别:
接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法设计模式是抽象类的一个典型应用,假设某个项目的所有Servlet类都要用相同的方式进行权限判断、记录访问日志和处理异常,那么就可以定义一个抽象的基类,让所有的Servlet都继承这个抽象基类,在抽象基类的service方法中完成权限判断、记录访问日志和处理异常的代码,在各个子类中只是完成各自的业务逻辑代码

实现会话跟踪的技术有哪些? 


答:由于HTTP协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记,为用户分配唯一的ID,下一次用户在请求中包含此ID,服务器据此判断到底是哪一个用户。 
①URL 重写:在URL中添加用户会话的信息作为请求的参数,或者将唯一的会话ID添加到URL结尾以标识一个会话。 
②设置表单隐藏域:将和会话跟踪相关的字段添加到隐式表单域中,这些信息不会在浏览器中显示但是提交表单时会提交给服务器。 
这两种方式很难处理跨越多个页面的信息传递,因为如果每次都要修改URL或在页面中添加隐式表单域来存储用户会话相关信息,事情将变得非常麻烦。 
③cookie:cookie有两种,一种是基于窗口的,浏览器窗口关闭后,cookie就没有了;另一种是将信息存储在一个临时文件中,并设置存在的时间。当用户通过浏览器和服务器建立一次会话后,会话ID就会随响应信息返回存储在基于窗口的cookie中,那就意味着只要浏览器没有关闭,会话没有超时,下一次请求时这个会话ID又会提交给服务器让服务器识别用户身份。会话中可以为用户保存信息。会话对象是在服务器内存中的,而基于窗口的cookie是在客户端内存中的。如果浏览器禁用了cookie,那么就需要通过下面两种方式进行会话跟踪。当然,在使用cookie时要注意几点:首先不要在cookie中存放敏感信息;其次cookie存储的数据量有限(4k),不能将过多的内容存储cookie中;再者浏览器通常只允许一个站点最多存放20个cookie。当然,和用户会话相关的其他信息(除了会话ID)也可以存在cookie方便进行会话跟踪。 
④HttpSession:在所有会话跟踪技术中,HttpSession对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建HttpSession,每个用户可以访问他自己的HttpSession。可以通过HttpServletRequest对象的getSession方法获得HttpSession,通过HttpSession的setAttribute方法可以将一个值放在HttpSession中,通过调用HttpSession对象的getAttribute方法,同时传入属性名就可以获取保存在HttpSession中的对象。与上面三种方式不同的是,HttpSession放在服务器的内存中,因此不要将过大的对象放在里面,即使目前的Servlet容器可以在内存将满时将HttpSession中的对象移到其他存储设备中,但是这样势必影响性能。添加到HttpSession中的值可以是任意Java对象,这个对象最好实现了Serializable接口,这样Servlet容器在必要的时候可以将其序列化到文件中,否则在序列化时就会出现异常。

三十四、字节流与字符流的区别

stream结尾都是字节流,reader和writer结尾都是字符流

两者的区别就是读写的时候一个是按字节读写,一个是按字符。

实际使用通常差不多。

在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。

只是读写文件,和文件内容无关的,一般选择字节流。

四十六、request 跟session的区别

1.他们的生命周期不同,

request对应的是一次请求,

session对应的是一次会话

2.request占用资源比较少,相对来说缺乏持续性,

而session资源消耗比较大,所以通常使用request来保存信息

存储过程(Stored Procedure)

  可以包含逻辑判断的sql语句集合。

  是经过预编译,存在于数据库中。

  通过调用指定存储过程的名字(可有参,可无参)来执行。

优点:

  简化了复杂的业务逻辑,根据需要可重复使用

  屏蔽了底层细节,不暴露表信息即可完成操作

  降低网络的通信量,多条语句可以封装成一个存储过程来执行

  设置访问权限来提高安全性

  提高执行效率,因为它是预编译以及存储在数据库中

缺点:

  可移植性差,相同的存储过程并不能跨多个数据库进行操作

  大量使用存储过程后,首先会使服务器压力增大,而且维护难度逐渐增加

 

存储过程的语法:

 

--下面是在oracle数据库下最基本的语法

--仅创建一个名为testProcedure 的无参的存储过程

--IS也可以是AS

--如果已经存在名为 testProcedure 的存储过程,下面的语法会出现 名称已被使用的错误

--解决办法:

--第一句可以写成 create or replace procedure testProcedure

--这样会替换原有的存储过程

--NULL表示任何可以正确执行的sql 语句,但至少一句


create procedure testProcedure

IS

BEGIN

NULL

END;

 

存储过程的参数的分类:

IN

OUT

INOUT

 

注意:

  存储过程之间可相互调用

  存储过程一般修改后,立即生效。

 

 

最后

以上就是苹果嚓茶为你收集整理的2019java后端面试看这篇就够了(二)Java面试最值得收藏的文章(共4部分):为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?6.线程7、 解析xml的几种技术优化事务14.jvmJava基础的全部内容,希望文章能够帮你解决2019java后端面试看这篇就够了(二)Java面试最值得收藏的文章(共4部分):为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?6.线程7、 解析xml的几种技术优化事务14.jvmJava基础所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(71)

评论列表共有 0 条评论

立即
投稿
返回
顶部