分享一些会遇到的JAVA语言面试题及题目解析
小标 2018-11-05 来源 : 阅读 849 评论 0

摘要:本文主要向大家介绍分享一些会遇到的JAVA语言面试题及题目解析了,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

本文主要向大家介绍分享一些会遇到的JAVA语言面试题及题目解析了,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。


不足的地方请大家多多指正,如有其它没有想到的常问面试题请大家多多评论,一起成长,感谢!~


String可以被继承吗?


因为Sting是这样定义的:public final class String extends Object,里边有final关键字,所以不能被继承。


接口能继承接口吗?


一个接口可以继承另一个接口,一个抽象类可以实现一个接口。


synchronized与static synchronized 的区别


synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。那么static synchronized恰好就是要控制类的所有实例的访问了,static synchronized是限制线程同时访问jvm中该类的所有实例同时访问对应的代码快。实际上,在类中某方法或某代码块中有 synchronized,那么在生成一个该类实例后,改类也就有一个监视快,放置线程并发访问改实例synchronized保护快,而static synchronized则是所有该类的实例公用一个监视快了,也也就是两个的区别了,也就是synchronized相当于 this.synchronized,而


static synchronized相当于Something.synchronized.


Spring BeanFactory与FactoryBean的区别


BeanFactory,以Factory结尾,表示它是一个工厂类(接口),用于管理Bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory,以Factory结尾,表示它是一个工厂类(接口),用于管理Bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。


FactoryBean以Bean结尾,表示它是一个Bean,不同于普通Bean的是:它是实现了FactoryBean接口的Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。


线程有返回值吗


在Java5之前,线程是没有返回值的,常常为了“有”返回值,破费周折,而且代码很不好写。或者干脆绕过这道坎,走别的路了。


现在Java终于有可返回值的任务(也可以叫做线程)了。


可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。


执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。


Volatile真的能解决线程并发吗?


用volatile修饰的变量 是java 语言提供的一种稍弱的同步机制,线程每次操作前都从主内存中刷新值,变量的更新操作也会及时的通知到其他线程。


如果把变量声明成volatile 类型 编译器和运行时都会注意变量值。


线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。


hashmap、concurrenthashmap底层实现和区别


Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。


ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment,默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁的几率,提高并发效率。


hashmap概述


HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。


值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。


HashMap的数据结构


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


HashMap的初始大小16个Entry,但是我在hashmap里面放了超过16个元素,扩容方法为resize()方法。


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


链表长度到了一定的长度,存储结构就会变成红黑树


hashmap工作原理:


通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。


ConcurrentHashMap


ConcurrentHashMap具体是怎么实现线程安全的呢,肯定不可能是每个方法加synchronized,那样就变成了HashTable。


从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。


在ConcurrentHashMap中,就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中:


ConcurrentHashMap中默认是把segments初始化为长度为16的数组。


ConcurrentHashMap的工作机制,通过把整个Map分为N个Segment(类似HashTable),可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。


一:spring基本概念


1)struts2是web框架,hibernate是orm框架


2)spring是容器框架,创建bean,维护bean之间的关系


3)spring可以管理web层,持久层,业务层,dao层,spring可以配置各个层的组件,并且维护各个层的关系


二:spring核心原理


1.IOC控制反转


概念:控制权由对象本身转向容器,由容器根据配置文件创建对象实例并实现各个对象的依赖关系。


核心:bean工厂


2.AOP面向切面编程


a.静态代理


根据每个具体类分别编写代理类


根据一个接口编写一个代理类


b.动态代理


针对一个方面编写一个InvocationHandler,然后借用JDK反射包中的Proxy类为各种接口动态生成相应的代理类


spring原理总结


1.使用spring ,没有new对象,我们把创建对象的任务交给spring框架


2.spring实际上是一个容器框架,可以配置各种bean(action/service/domain/dao),并且可以维护bean与bean的关系,当我们需要使用某个bean的时候,我们可以getBean(id),使用即可.


jvm调优(简单概括)


1:建议用64位操作系统,Linux下64位的jdk比32位jdk要慢一些,但是吃得内存更多,吞吐量更大。


2:XMX和XMS设置一样大,MaxPermSize和MinPermSize设置一样大,这样可以减轻伸缩堆大小带来的压力。


3:调试的时候设置一些打印JVM参数,如-XX:+PrintClassHistogram-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintHeapAtGC-Xloggc:log/gc.log,这样可以从gc.log里看出一些端倪出来。


4:系统停顿的时候可能是GC的问题也可能是程序的问题,多用jmap和jstack查看,或者killall-3java,然后查看java控制台日志,能看出很多问题。有一次,网站突然很慢,jstack一看,原来是自己写的URLConnection连接太多没有释放,改一下程序就OK了。


5:仔细了解自己的应用,如果用了缓存,那么年老代应该大一些,缓存的HashMap不应该无限制长,建议采用LRU算法的Map做缓存,LRUMap的最大长度也要根据实际情况设定。


6:垃圾回收时promotionfailed是个很头痛的问题,一般可能是两种原因产生,第一个原因是救助空间不够,救助空间里的对象还不应该被移动到年老代,但年轻代又有很多对象需要放入救助空间;第二个原因是年老代没有足够的空间接纳来自年轻代的对象;这两种情况都会转向FullGC,网站停顿时间较长。第一个原因我的最终解决办法是去掉救助空间,设置-XX:SurvivorRatio=65536-XX:MaxTenuringThreshold=0即可,第二个原因我的解决办法是设置CMSInitiatingOccupancyFraction为某个值(假设70),这样年老代空间到70%时就开始执行CMS,年老代有足够的空间接纳来自年轻代的对象。


7:不管怎样,永久代还是会逐渐变满,所以隔三差五重起java服务器是必要的,我每天都自动重起。


8:采用并发回收时,年轻代小一点,年老代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿。


gc算法


垃圾收集算法


标记 -清除算法


“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。


它的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。


复制算法


“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。


这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,持续复制长生存期的对象则导致效率降低。


标记-压缩算法


复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。


根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存


分代收集算法


GC分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。


“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。


垃圾收集器


如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现


Serial收集器


串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)


参数控制:-XX:+UseSerialGC 串行收集器


spring中bean的作用域


Spring 3中为Bean定义了5中作用域,分别为singleton(单例)、prototype(原型)、request、session和global session,5种作用域说明如下:


singleton:单例模式,Spring IoC容器中只会存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一对象。Singleton作用域是Spring中的缺省作用域,也可以显示的将Bean定义为singleton模式,配置为:


prototype:原型模式,每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态,而singleton全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。


request:在一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。


,针对每一次Http请求,Spring容器根据该bean的定义创建一个全新的实例,且该实例仅在当前Http请求内有效,而其它请求无法看到当前请求中状态的变化,当当前Http请求结束,该bean实例也将会被销毁。


session:在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。


,同Http请求相同,每一次session请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session请求内有效,请求结束,则实例将被销毁。


global Session:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。


spring中单例的bean是不是线程安全的


Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。


最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。


java8中stream有几种


Java中的Stream的所有操作都是针对流的,所以,使用Stream必须要得到Stream对象:


1、初始化一个流:


Stream stream = Stream.of("a", "b", "c");


2、数组转换为一个流:


String [] strArray = new String[] {"a", "b", "c"};


stream = Stream.of(strArray);


或者


stream = Arrays.stream(strArray);


3、集合对象转换为一个流(Collections):


List list = Arrays.asList(strArray);


stream = list.stream();


spring springboot springcloud 的关系与区别


Spring Boot框架的核心就是自动配置,只要存在相应的jar包,Spring就帮我们自动配置


1、Spring boot 是 Spring 的一套快速配置脚手架,可以基于spring boot 快速开发单个微服务;Spring Cloud是一个基于Spring Boot实现的云应用开发工具;


2、Spring boot专注于快速、方便集成的单个个体,Spring Cloud是关注全局的服务治理框架;


3、spring boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring boot来实现。


4、Spring boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring boot,属于依赖的关系。


过滤器和拦截器的区别


拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。


过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上。过滤器可附加到一个或多个servlet或JSP页面上,并且可以检查进入这些资源的请求信息。


zookeeper的数据结构


ZooKeeper这种数据结构有如下这些特点:


1. 每个子目录项如NameService都被称作znode,这个znode是被它所在的路径唯一标识,如Server1这个znode的标识为/NameService/Server1


2. znode可以有子节点目录,并且每个znode可以存储数据,注意EPHEMERAL类型的目录节点不能有子节点目录


3. znode是有版本的,每个znode中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据


4. znode可以是临时节点,一旦创建这个znode的客户端与服务器失去联系,这个znode也将自动删除,ZooKeeper的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态成为session,如果znode是临时节点,这个session失效,znode也就被删除.


5. znode的目录名可以自动编号,如App1已经存在,再创建的话,将会自动命名为App2.


6. znode可以被监控,包括这个目录节点中存储的数据被修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是ZooKeeper的核心特性.


java8中实现for循环continue的方式是


在使用foreach()处理集合时不能使用break和continue这两个方法,也就是说不能按照普通的for循环遍历集合时那样根据条件来中止遍历,而如果要实现在普通for循环中的效果时,可以使用return来达到,也就是说如果你在一个方法的lambda表达式中使用return时,这个方法是不会返回的,而只是执行下一次遍历


索引的数据结构


(1)B-Tree


(2)B+Tree


(3)检索过程:首先在最上层节点进行二分查找,如果找不到则去对应左子节点或右子节点进行二分查找,以此递归


(4)B-Tree和B+Tree区别


—内节点不存储data,只存储key


—叶子节点不存储指针(叶节点和内节点大小一般不相同)


—叶子节点具有指向相邻叶子节点的指针(方便区间访问)


(5)索引设计思路


—索引本身也很大,不能直接在主存中存储,而是存在磁盘上。一个好的索引数据结构应该最大程度减少磁盘IO。


—数据库设计者巧妙地将【树节点】用【单位页】进行对应存储,这样一个节点的载入只需要一次磁盘IO。在B-Tree定义中,检索一次最多需要访问h(树高度)个节点,相应地最多需要h-1次IO(根节点常驻内存),时间复杂度O(h)= O(logdN),h为树高度,d为单个节点中Key数量,d越大,索引性能越好。一般d比较大,即横向比较长,h比较小,通常不超过3,因此B-Tree作为索引结构效率非常高。


(6)为什么不用二叉树或红黑树作为索引数据结构?


h较深,逻辑上相邻的父子节点在物理上可能很远,无法利用局部性


(7)为什么B+Tree优于B-Tree?


因为d越大,索引性能越好,dmax=floor(pagesize/(keysize+datasize+pointsize)),由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能


Mysql索引有哪些


从数据结构角度


1、B+树索引(O(log(n))):关于B+树索引,可以参考 MySQL索引背后的数据结构及算法原理


2、hash索引:


a 仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询


b 其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引


c 只有Memory存储引擎显示支持hash索引


3、FULLTEXT索引(现在MyISAM和InnoDB引擎都支持了)


4、R-Tree索引(用于对GIS数据类型创建SPATIAL索引)


从物理存储角度


1、聚集索引(clustered index)


2、非聚集索引(non-clustered index)


从逻辑角度


1、主键索引:主键索引是一种特殊的唯一索引,不允许有空值


2、普通索引或者单列索引


3、多列索引(复合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合


4、唯一索引或者非唯一索引


5、空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建


CREATE TABLE table_name[col_name data type]


[unique|fulltext|spatial][index|key][index_name](col_name[length])[asc|desc]


1、unique|fulltext|spatial为可选参数,分别表示唯一索引、全文索引和空间索引;


2、index和key为同义词,两者作用相同,用来指定创建索引


3、col_name为需要创建索引的字段列,该列必须从数据表中该定义的多个列中选择;


4、index_name指定索引的名称,为可选参数,如果不指定,MYSQL默认col_name为索引值;


5、length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度;


6、asc或desc指定升序或降序的索引值存储


ORM


大概地说,这类框架的是为了将类对象和关系建立映射,在应用程序和数据库的IO之间建立一个中间层,在程序中只需要直接操作对象(数据库中对象的增删改查),而不用去关心数据库中表的列啊,关系啊什么的


相关技术:JPA 、hibernate等 没必要自己写,自己写出来的也不一定有他们的好,并且一些判断、提示都没有他们的完善


事务的传播行为和隔离级别[transaction behavior and isolated level]


Spring中事务的定义:


一、Propagation :


key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:


PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。


PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。


PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。


PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。


PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。


PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。


很多人看到事务的传播行为属性都不甚了解,我昨晚看了j2ee without ejb的时候,看到这里也不了解,甚至重新翻起数据库系统的教材书,但是也没有找到对这个的分析。今天搜索,找到一篇极好的分析文章,虽然这篇文章是重点分析PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRED_NESTED的


解惑 spring 嵌套事务


这里一个个分析


1: PROPAGATION_REQUIRED


加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务


比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,


ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA


的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。


这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被


提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚


2: PROPAGATION_SUPPORTS


如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行


这就跟平常用的普通非事务的代码只有一点点区别了。不理这个,因为我也没有觉得有什么区别


3: PROPAGATION_MANDATORY


必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。


4: PROPAGATION_REQUIRES_NEW


这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,


那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,


他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在


两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,


如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。


5: PROPAGATION_NOT_SUPPORTED


当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,


那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。


6: PROPAGATION_NEVER


不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,


那么ServiceB.methodB就要抛出异常了。


7: PROPAGATION_NESTED


理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,


而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。


二、Isolation Level(事务隔离等级):


1、Serializable:最严格的级别,事务串行执行,资源消耗最大;


2、REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。


3、READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。


4、Read Uncommitted:保证了读取过程中不会读取到非法数据。


隔离级别在于处理多事务的并发问题。


我们知道并行可以提高数据库的吞吐量和效率,但是并不是所有的并发事务都可以并发运行,这需要查看数据库教材的可串行化条件判断了。


这里就不阐述。


我们首先说并发中可能发生的3中不讨人喜欢的事情


1: Dirty reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。


2: non-repeatable reads--数据不可重复读。比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。


3: phantom reads--幻象读数据,这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select account.id where account.name="ppgogo*",第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由"dd"改成"ppgogo1",结果取出来了7个数据。


Dirty reads non-repeatable reads phantom reads


Serializable 不会 不会 不会


REPEATABLE READ 不会 不会 会


READ COMMITTED 不会 会 会


Read Uncommitted 会 会 会


三、readOnly


事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。


四、Timeout


在事务属性中还有定义“timeout”值的选项,指定事务超时为几秒。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释。


web安全


前后端约定好加密方式,通过修改request 的parameter


继承HttpServletRequestWrapper,其实就是上面那种方法多了一层继承,将你的重复工作交予了它,你也可以这样做,


全名为:javax.servlet.http.HttpServletRequestWrapper,看来也是一个扩展的通用接口,也就是会对request做一次包装,OK;跟着进去发现它可以处理类似request一样的差不多的内容,在这个基础上做了一次包装,你可以认为他就是对你自己new的那个,多了一层简单扩展实现,而你再这个基础上,可以继续继承和重写。


OK,此时你要重写如何重写呢,比如我们要重写一个getParameter方法和getParameterValues方法,其余的方法保持和原来一致,我们在子类中,自己定义一个Map用来放参数,结合request本身的参数,加上外部其他自定义的参数,做成一个新的参数表。


我们用到的是反射机制 虽然网上有说我们是在裸奔,但是还没来得及改。。


java并发包


1、ConcurrentHashMap


ConcurrentHashMap其实就是线程安全版本的hashMap。前面我们知道HashMap是以链表的形式存放hash冲突的数据,以数组形式存放HashEntry等hash出来不一致的数据。为了保证容器的数据一致性,需要加锁。HashMap的实现方式是,只有put和remove的时候会引发数据的不一致,那为了保证数据的一致性,我在put和remove的时候进行加锁操作。但是随之而来的是性能问题,因为key-value形式的数据,读写频繁是很正常的,也就意味着我有大量数据做读写操作时会引发长时间的等待。为了解决这个问题,Java并发包问我们提供了新的思路。在每一个HashEntry上加一把锁,对于hash冲突的数据,因为采用链表存储,公用一把锁。这样我才在做不同hash数值的数据时,则是在不同的锁环境下执行,基本上是互不干扰的。在最好情况下,可以保证16个线程同时进行无阻塞的操作(HashMap的默认HashEntry是16,亦即默认的数组大小是16)。


那ConcurrentHashMap是如何保证数据操作的一致性呢?对于数据元素的大小,ConcurrentHashMap将对应数组(HashEntry的长度)的变量为voliate类型的,也就是任何HashEntry发生变更,所有的地方都会知道数据的大小。对于元素,如何保证我取出的元素的next不发生变更呢?(HashEntry中的数据采用链表存储,当读取数据的时候可能又发生了变更),这一点,ConcurrentHashMap采取了最简单的做法,hash值、key和next取出后都为final类型的,其next等数据永远不会发生变更。


2、CopyOnWriteArrayList


同样的,CopyOnWriteArrayList是线程安全版本的ArrayList。和ArrayList不同的是,CopyOnWriteArrayList默认是创建了一个大小为0的容器。通过ReentrantLock来保证线程安全。CopyOnWriteArrayList其实每次增加的时候,需要新创建一个比原来容量+1大小的数组,然后拷贝原来的元素到新的数组中,同时将新插入的元素放在最末端。然后切换引用。


针对CopyOnWriteArrayList,因为每次做插入和删除操作,都需要重新开辟空间和复制数组元素,因此对于插入和删除元素,CopyOnWriteArrayList的性能远远不如ArrayList,但是每次读取的时候,CopyOnWriteArrayList在不加锁的情况下直接锁定数据,会快很多(但是可能会引发脏读),对于迭代,CopyOnWriteArrayList会生成一个快照数组,因此当迭代过程中出现变化,快照数据没有变更,因此读到的数据也是不会变化的。在读多写少的环境下,CopyOnWriteArrayList的性能还是不错的。


3、CopyOnWriteArraySet


CopyOnWriteArraySet是基于CopyOnWriteArrayList实现的。但是CopyOnWriteArraySet鉴于不能插入重复数据,因此每次add的时候都要遍历数据,性能略低于CopyOnWriteArrayList。


4、ArrayBlockingQueue


ArrayBlockingQueue是基于数组实现的一个线程安全的队列服务,其相关的功能前面我们已经用到过了,这里就不多提了。


5、Atomic类,如AtomicInteger、AtomicBoolean


线程问题


1. 进程和线程之间有什么不同?


一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。


而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单 一进程。


线程可以被称为轻量级进程。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。


2. 多线程编程的好处是什么?


在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。


多个线程共享堆内存(heap memory),因此创建多个线程去执行一些任务会比创建多个进程更好。


举个例子,Servlets比CGI更好,是因为Servlets支持多线程而 CGI不支持。


3. 用户线程和守护线程有什么区别?


当我们在Java程序中创建一个线程,它就被称为用户线程。


一个守护线程是在后台执行并且不会阻止JVM终止的线程。


当没有用户线程在运行的时候,JVM关闭程序并且退出。


一个守护线程创建的子线程依然是守护线程。


4. 我们如何创建一个线程?


有两种创建线程的方法:


一是实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象;


二是直接继承Thread类。


5. 有哪些不同的线程生命周期?


当我们在Java程序中新建一个线程时,它的状态是New。当我们调用线程的start()方法时,状态被改变为Runnable。


线程调度器会为Runnable线程池中的线程分配CPU时间并且讲它们的状态改变为Running。


其他的线程状态还有Waiting,Blocked 和Dead。


6. 可以直接调用Thread类的run()方法么?


当然可以,但是如果我们调用了Thread的run()方法,它的行为就会和普通的方法一样,为了在新的线程中执行我们的代码,必须使用Thread.start()方法。


7. 如何让正在运行的线程暂停一段时间?


我们可以使用Thread类的Sleep()方法让线程暂停一段时间。需要注意的是,这并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为Runnable,并且根据线程调度,它将得到执行。


8. 你对线程优先级的理解是什么?


每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,


但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。


我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。


线程优先级是一个int变量(从 1-10),1代表最低优先级,10代表最高优先级。


9. 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?


线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。


一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。


时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。


分配CPU时间可以基于线程优先级或者线程等待的时间。


线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。


10. 在多线程中,什么是上下文切换(context-switching)?


上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。


上下文切换是多任务操作系统和多线程环境的基本特征。


11. 你如何确保main()方法所在的线程是Java程序最后结束的线程?


我们可以使用Thread类的joint()方法来确保所有程序创建的线程在main()方法退出前结束。


12.线程之间是如何通信的?


当线程间是可以共享资源时,线程间通信是协调它们的重要的手段。


Object类中wait()\notify()\notifyAll()方法可以用于线程间通信关于资源的锁的状态。


13.为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object类里?


Java的每个对象中都有一个锁(monitor,也可以成为监视器) 并且wait(),


notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用。


在Java的线程中并没有可供任何对象使用的锁和同步器。


这就是为什么这些方法是Object类的一部分,这样Java的每一个类都有用于线程间通信的基本方法


14. 为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?


当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,


接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象 上的notify()方法。


同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象 锁。


由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。


15. 为什么Thread类的sleep()和yield()方法是静态的?


Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。‘


所以在其他处于等待状态的线程上调用这些方法是没有意义的。


这 就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。


16.如何确保线程安全?


在Java中可以有很多方法来保证线程安全——同步,使用原子类(atomic concurrent classes),实现并发锁,使用volatile关键字,使用不变类和线程安全类。


17. volatile关键字在Java中有什么作用?


当我们使用volatile关键字去修饰变量的时候,所以线程都会直接读取该变量并且不缓存它。


这就确保了线程读取到的变量是同内存中是一致的。


18. 同步方法和同步块,哪个是更好的选择?


同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。


同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。


19.如何创建守护线程?


使用Thread类的setDaemon(true)方法可以将线程设置为守护线程,


需要注意的是,需要在调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。


20. 什么是ThreadLocal


ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,


所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。


每个线程都会拥有他们自己的Thread变量,它们可以使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值。


ThreadLocal实例通常是希望它们同线程状态关联起来是private static属性。


21. 什么是Thread Group?为什么建议使用它?


ThreadGroup是一个类,它的目的是提供关于线程组的信息。


ThreadGroup API比较薄弱,它并没有比Thread提供了更多的功能。


它有两个主要的功能:一是获取线程组中处于活跃状态线程的列表;


二是设置为线程设置未捕获异常 处理器(ncaught exception handler)。


但在Java 1.5中Thread类也添加了setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 方法,


所以ThreadGroup是已经过时的,不建议继续使用。


22. 什么是Java线程转储(Thread Dump),如何得到它?


线程转储是一个JVM活动线程的列表,它对于分析系统瓶颈和死锁非常有用。


有很多方法可以获取线程转储——使用Profiler,Kill -3命令,jstack工具等等。我更喜欢jstack工具,因为它容易使用并且是JDK自带的。由于它是一个基于终端的工具,所以我们可以编写一些脚本 去定时的产生线程转储以待分析。读这篇文档可以了解更多关于产生线程转储的知识。


23. 什么是死锁(Deadlock)?如何分析和避免死锁?


死锁是指两个以上的线程永远阻塞的情况,这种情况产生至少需要两个以上的线程和两个以上的资源。


分析死锁,我们需要查看Java应用程序的线程转储。我们需要找出那些状态为BLOCKED的线程和他们等待的资源。每个资源都有一个唯一的id,用这个id我们可以找出哪些线程已经拥有了它的对象锁。


避免嵌套锁,只在需要的地方使用锁和避免无限期等待是避免死锁的通常办法,阅读这篇文章去学习如何分析死锁。


24. 什么是Java Timer类?如何创建一个有特定时间间隔的任务?


java.util.Timer是一个工具类,可以用于安排一个线程在未来的某个特定时间执行。Timer类可以用安排一次性任务或者周期任务。


java.util.TimerTask是一个实现了Runnable接口的抽象类,我们需要去继承这个类来创建我们自己的定时任务并使用Timer去安排它的执行。


这里有关于java Timer的例子。


25. 什么是线程池?如何创建一个Java线程池?


一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行的任务的队列。


java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。线程池例子展现了如何创建和使用线程池,或者阅读ScheduledThreadPoolExecutor例子,了解如何创建一个周期任务。


Java并发面试问题


1. 什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?


原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。


int++并不是一个原子操作,所以当一个线程读取它的值并加1时,另外一个线程有可能会读到之前的值,这就会引发错误。


为了解决这个问题,必须保证增加操作是原子的,在JDK1.5之前我们可以使用同步技术来做到这一点。到 JDK1.5,java.util.concurrent.atomic包提供了int和long类型的装类,它们可以自动的保证对于他们的操作是原子的 并且不需要使用同步。可以阅读这篇文章来了解Java的atomic类。


2. Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?


Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。


它的优势有:


可以使锁更公平


可以使线程在等待锁的时候响应中断


可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间


可以在不同的范围,以不同的顺序获取和释放锁


阅读更多关于锁的例子


3. 什么是Executors框架?


Executor框架同java.util.concurrent.Executor 接口在Java 5中被引入。Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。


无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用Executors框架可以非常方便的创建一个线程池,阅读这篇文章可以了解如何使用Executor框架创建一个线程池。


4. 什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?


java.util.concurrent.BlockingQueue的特性是:当队列是空的时,从队列中获取或删除元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。


阻塞队列不接受空值,当你尝试向队列中添加空值的时候,它会抛出NullPointerException。


阻塞队列的实现都是线程安全的,所有的查询方法都是原子的并且使用了内部锁或者其他形式的并发控制。


BlockingQueue 接口是java collections框架的一部分,它主要用于实现生产者-消费者问题。


阅读这篇文章了解如何使用阻塞队列实现生产者-消费者问题。


5. 什么是Callable和Future


Java 5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很相似,但它可以返回一个对象或者抛出一个异常。


Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于 Callable任务是并行的,我们必须等待它返回的结果。java.util.concurrent.Future对象为我们解决了这个问题。在线程池 提交Callable任务后返回了一个Future对象,使用它我们可以知道Callable任务的状态和得到Callable返回的执行结果。 Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果。


阅读这篇文章了解更多关于Callable,Future的例子。


6. 什么是FutureTask


FutureTask是Future的一个基础实现,我们可以将它同Executors使用处理异步任务。通常我们不需要使用FutureTask 类,单当我们打算重写Future接口的一些方法并保持原来基础的实现是,它就变得非常有用。我们可以仅仅继承于它并重写我们需要的方法。阅读Java FutureTask例子,学习如何使用它。


7.什么是并发容器的实现?


Java集合类都是快速失败的,这就意味着当集合被改变且一个线程在使用迭代器遍历集合的时候,迭代器的next()方法将抛出ConcurrentModificationException异常。


并发容器支持并发的遍历和并发的更新。


主要的类有ConcurrentHashMap, CopyOnWriteArrayList 和CopyOnWriteArraySet,阅读这篇文章了解如何避免ConcurrentModificationException。


8. Executors类是什么?


Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法。


Executors可以用于方便的创建线程池。


冒泡排序的实现原理


冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。


它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。


这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故名。


冒泡排序算法的原理如下:(从后往前)


比较相邻的元素。如果第一个比第二个大,就交换他们两个。


对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。


针对所有的元素重复以上的步骤,除了最后一个。


持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。


二叉树遍历的三种方法


1、递归法


这是思路最简单的方法,容易想到并且容易实现。递归的终止条件是当前节点是否为空。首先递归调用遍历左子树,然后访问当前节点,最后递归调用右子树。代码如下:


[cpp] view plain copy


//recursive


class Solution1 {


public:


vector inorderTraversal(TreeNode* root) {


vector ret;


if(root==NULL)return ret;


inorderHelper(ret,root);


return ret;


}


private:


void inorderHelper(vector& ret,TreeNode* root)


{


if(root==NULL)return;


inorderHelper(ret,root->left);


ret.push_back(root->val);


inorderHelper(ret,root->right);


}


};


2、迭代法


在迭代方法中,从根节点开始找二叉树的最左节点,将走过的节点保存在一个栈中,找到最左节点后访问,对于每个节点来说,它都是以自己为根的子树的根节点,访问完之后就可以转到右儿子上了。代码如下:


[cpp] view plain copy


//iterate,using a stack


class Solution2 {


public:


vector inorderTraversal(TreeNode* root) {


vector ret;


if(root==NULL)return ret;


TreeNode *curr=root;


stack st;


while(!st.empty()||curr!=NULL)


{


while(curr!=NULL)


{


st.push(curr);


curr=curr->left;


}


curr=st.top();


st.pop();


ret.push_back(curr->val);


curr=curr->right;


}


return ret;


}


};


这种方法时间复杂度是O(n),空间复杂度也是O(n)。


3、Morris法


这种方法是Morris发明的,看完之后感觉精妙无比。这种方法不使用递归,不使用栈,O(1)的空间复杂度完成二叉树的遍历。这种方法的基本思路就是将所有右儿子为NULL的节点的右儿子指向后继节点(对于右儿子不为空的节点,右儿子就是接下来要访问的节点)。这样,对于任意一个节点,当访问完它后,它的右儿子已经指向了下一个该访问的节点。对于最右节点,不需要进行这样的操作。注意,这样的操作是在遍历的时候完成的,完成访问节点后会把树还原。整个循环的判断条件为当前节点是否为空。例如上面的二叉树,遍历过程如下(根据当前节点c的位置):


(1)当前节点为10,因为左儿子非空,不能访问,找到c的左子树的最右节点p:


结果:[]


(2)找节点c的左子树的最右节点有两种终止条件,一种右儿子为空,一种右儿子指向当前节点。下面是右儿子为空的情况,这种情况先要构造,将节点p的右儿子指向后继节点c,然后c下移:


结果:[]


(3)当前节点c的左儿子为空,进行访问。访问后将c指向右儿子(即后继节点):


结果:[5]


(4)继续寻找左子树的最右节点,这次的终止条件是最右节点为当前节点。这说明当前节点的左子树遍历完毕,访问当前节点后,还原二叉树,将当前节点指向后继节点:


结果:[5,10]


(5)重复上述过程,直到c指向整棵二叉树的最右节点:


左儿子为空,进行访问,c转到右儿子。右儿子为空,不满足判断条件,循环结束,遍历完成。结果如下:


[5,10,6,15,2]


这就是Morris方法,时间复杂度为O(n),空间复杂度是O(1)。代码如下:


[cpp] view plain copy


//Morris traversal,without a stack


class Solution3 {


public:


vector inorderTraversal(TreeNode* root) {


vector ret;


if(root==NULL)return ret;


TreeNode *curr=root;


TreeNode *pre;


while(curr)


{


if(curr->left==NULL)


{


ret.push_back(curr->val);


curr=curr->right;


}


else


{


pre=curr->left;


while(pre->right&&pre->right!=curr)


pre=pre->right;


if(pre->right==NULL)


{


pre->right=curr;


curr=curr->left;


}


else


{


ret.push_back(curr->val);


pre->right=NULL;


curr=curr->right;


}


}


}


return ret;


}


};


TCP三次握手


第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。


第二次


第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;


第三次


第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。


java数据结构


数据结构分类:线性结构和非线性结构


问题一:


什么是线性和非线性;


我个人的理解是:数据结构中线性结构指的是数据元素之间存在着“一对一”的线性关系的数据结构;


线性结构包括:数组,链表,队列,栈;


非线性结构包括:树,图,表;


详解:


一.线性结构


1.数组


特点:我们都知道数组中的元素在内存中连续存储的,可以根据是下标快速访问元素,因此,查询速度很快,然而插入和删除时,需要对元素移动空间,比较慢。


数组使用场景:频繁查询,很少增加和删除的情况。


2.链表


特点:元素可以不连续内存中,是以索引将数据联系起来的,当查询元素的时候需要从头开始查询,所以效率比较低,然而添加和删除的只需要修改索引就可以了


使用场景:少查询,需要频繁的插入或删除情况


3.队列


特点:先进先出,


使用场景:多线程阻塞队列管理非常有用


4.栈


特点:先进后出,就像一个箱子,


使用场景:实现递归以及表示式


5.数组与链表的区别


数组连续,链表不连续(从数据存储形式来说)


数组内存静态分配,链表动态分配


数组查询复杂度0(1),链表查询复杂度O(n)


数组添加或删除,复杂度o(n),链表添加删除,复杂度O(1)


数组从栈中分配内存。链表从堆中分配内存。


Java内存溢出问题的定位过程


对于java.lang.OutOfMemoryError: PermGen space 这种情况更多的是靠程序猿的经验来解决:


PermGen space的全称是Permanent Generation space,是指内存的永久保存区域, 这块内存主要是被JVM存放Class和Meta信息的,Class在被Load时就会被放到PermGen space中, 它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对 PermGen space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGen space错误。


通过上面的描述就可以得出:如果要加载的class与jar文件大小超过-XX:MaxPermSize就有可能会产生java.lang.OutOfMemoryError: PermGen space 。


换句话说-XX:MaxPermSize的大小要超过class与jar的大小。通常-XX:MaxPermSize为-Xmx的1/8。


对于java.lang.OutOfMemoryError: Java heap space 可能定位的过程就需要折腾一翻了:


虽然各种java虚拟机的实现机制不一,但是heap space内存溢出产生的原因相同:那就是堆内存不够虚拟机分配了。


我对java虚拟机的实现不感兴趣,对各种虚拟机的内存分配机制与gc的交互关系也不了解。但是我大致认为内存分配机制与gc是有联系的,也就是说内存不够分配时gc肯定也释放不了堆内存。从这一点出发,我们就需要找为什么gc释放不了堆内存。通常来说释放不了是因为内存还在使用。对于java对象产生的堆内存占用,只要其不再被继续引用gc是能够顺利回收的(对于通过本地方法调用,即JNI调用产生内存泄露的情况暂不考虑)。


问题的关键就找到了,当产生heap space内存溢出时,堆内存中对象数量过多的就可能是问题的根源了。例外的情况是,程序确实需要那么多内存,这时就要考虑增大堆内存。


java sleep和wait的区别


首先,要记住这个差别,“sleep是Thread类的方法,wait是Object类中定义的方法”。尽管这两个方法都会影响线程的执行行为,但是本质上是有区别的。


Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep不会让线程释放锁。如果能够帮助你记忆的话,可以简单认为和锁相关的方法都定义在Object类中,因此调用Thread.sleep是不会影响锁的相关行为。


Thread.sleep和Object.wait都会暂停当前的线程,对于CPU资源来说,不管是哪种方式暂停的线程,都表示它暂时不再需要CPU的执行时间。OS会将执行时间分配给其它线程。区别是,调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间。


线程的状态参考 Thread.State的定义。新创建的但是没有执行(还没有调用start())的线程处于“就绪”,或者说Thread.State.NEW状态。


Thread.State.BLOCKED(阻塞)表示线程正在获取锁时,因为锁不能获取到而被迫暂停执行下面的指令,一直等到这个锁被别的线程释放。BLOCKED状态下线程,OS调度机制需要决定下一个能够获取锁的线程是哪个,这种情况下,就是产生锁的争用,无论如何这都是很耗时的操作。


字符串哈西相等,equals相等吗反过来呢


在java中,equals和hashcode是有设计要求的,equals相等,则hashcode一定相等,反之则不然。


为何会有这样的要求?


在集合中,比如HashSet中,要求放入的对象不能重复,怎么判定呢?


首先会调用hashcode,如果hashcode相等,则继续调用equals,也相等,则认为重复。


如果重写equals后,如果不重写hashcode,则hashcode就是继承自Object的,返回内存编码,这时候可能出现equals相等,而hashcode不等,你的对象使用集合时,就会等不到正确的结果


Java中的几种设计模式


如果从事JAVA相关的开发,都不可避免的要用到抽象和封装,这是JAVA的一个特点,同时也是每个开发者必须掌握的,JAVA是这样,Android更是如此。而设计模式就是告诉我们应该如何写出高效且更具应用性和拓展性的代码,最近也是学习了几类比较常用的设计模式,下面一一列举出来,虽然说的不细,但是应该知道的我会做个总结。


#####单例设计模式#####


单例设计模式的一般定义:一个类中只允许有一个实例。


实现思路:让类的构造方法私有化,同时提供一个静态方法去实例化这个类。


代码 :


static class SingleTon {


private static SingleTon instance;


private SingleTon(){};


public static SingleTon newInstance() {


if(instance==null) {


synchronized (SingleTon.class) {


if(instance==null) {


ins和tance=new SingleTon();


}


}


}


return instance;


}


这是一个较为标准的单例模式,为了安全我给他加了锁,然而这样写并不是最好的写法。单例模式有两种写法,懒汉写法和饿汉写法。


懒汉式:在静态方法中初始化。时间换空间。(不推荐,时间很重要)


饿汉式:在声明对象就初始化。空间换时间。(推荐,空间不是问题)


所以,在实际开发中用的更多的是饿汉写法,可以对这个类加锁,在变量声明的时候就初始化。具体如何实现这里我就不介绍了,可以自己去实现。


#####简单工厂设计模式#####


简单工厂设计模式的一般定义:简单工厂又叫静态工厂,由一个工厂对象决定创建哪一个产品对象。


实现思路:写一个类,让他制造出我们想要的对象。


代码:


public class 学生工厂 {


public static 学生 getStudent(int type) {


switch (type) {


case 学生类型.学神:


return new 学神();


case 学生类型.学霸:


return new 学霸();


case 学生类型.学弱:


return new 学弱();


case 学生类型.学    

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注编程语言JAVA频道!


本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程