Java面试题

IO和NIO的区别是什么?

1.面向流与面向缓冲

Java IO和NIO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。

Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。

Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

2.阻塞与非阻塞IO

Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。

Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

3.选择器(Selectors)

Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

Redis是什么?

Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。

因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过10万次读写操作,是已知性能最快的Key-Value DB。

Redis的出色之处不仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能。

比方说用他的List来做FIFO双向链表,实现一个轻量级的高性能消息队列服务,用他的Set可以做高性能的tag系统等等。

Redis相比memcached有哪些优势?

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

②redis的速度比memcached快很多

③ redis可以持久化其数据

Redis有哪几种数据淘汰策略?

Redis有哪几种数据淘汰策略?有noeviction、allkeys-lru、allkeys-random、volatile-random、volatile-ttl五种淘汰策略,下面我们分别介绍。

noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(部分的写入指令,但DEL和几个例外)

allkeys-lru:尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。

volatile-lru:尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。

allkeys-random:回收随机的键使得新添加的数据有空间存放。

volatile-random:回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。

volatile-ttl:回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放

Redis所有数据放到内存中的原因是什么?

Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。

所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

Redis适合在哪些场景使用?

①会话缓存(Session Cache)

最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Mem cached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?

幸运的是,随着Redis这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。

②全页缓存(FPC)

除基本的会话token之外,Redis 还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。

再次以Magento为例,Magento 提供一个插件来使用Redis 作为全页缓存后端。

此外,对WordPress的用户来说,Pantheon 有一个非常好的插件wp redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

③队列

Reids在内存存储引擎领域的一-大优点是提供list和set操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对list 的push/pop操作。

如果你快速的在Google中搜索“Redis queues",你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery 有一个后台就是使用Redis作为broker,你可以从这里去查看。

④排行榜/计数器

Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构。

所以,我们要从排序集合中获取到排名最靠前的10个用户-我们称之为“user_scores", 我们只需要像下面一样执行即可:

当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:

ZRANGE user scores 0 10 WITHSCORES

Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。

⑤发布/订阅

最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!

Redis集群方案哪些方案?

①codis

目前用的最多的集群方案,基本和twemproxy-致的效果,但它支持在节点数量改变情况下,旧节点数据可恢复到新hash节点。

②redis

cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。

③在业务代码层实现

起几个毫无关联的redis实例,在代码层,对key进行hash计算,然后去对应的redis实例操作数据。这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。

类的加载机制是什么?

Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能。

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。在Java中,类装载器把一个类装入JVM中,要经过以下步骤:

1)装载:查找和导入Class文件;

2)链接:把类的二进制数据合并到JRE中;

A)校验:检查载入Class文件数据的正确性;

B)准备:给类的静态变量分配存储空间;

C)解析:将符号引用转成直接引用;

3)初始化:对类的静态变量,静态代码块执行初始化操作

Java程序可以动态扩展是由运行期动态加载和动态链接实现的;比如:如果编写一个使用接口的应用程序,可以等到运行时再指定其实际的实现(多态),解析过程有时候还可以在初始化之后执行;比如:动态绑定(多态)。

由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(Java.lang.ClassLoader的子类)。 

servlet的生命周期及servlet常用方法

1)init()方法

在Servlet的生命周期中,仅执行一次init()方法,它是在服务器装入Servlet 时执行的,可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入 Servlet。无论有多少客户机访问Servlet,都不会重复执行init();

2)service()方法

它是Servlet的核心,每当一个客户请求一个HttpServlet对象,该对象的 Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest) 对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存 在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能。

3)destroy()方法

仅执行一次,在服务器端停止且卸载Servlet时执行该方法,有点类似于C++的 delete方法。一个Servlet在运行service()方法时可能会产生其他的线程,因 此需要确认在调用destroy()方法时,这些线程已经终止或完成。

Servlet的生命周期是由Servlet容器来控制的,它始于装入Web服务器的内存时,并在终止或重新装入Servlet时结束。

在代码中,Servlet生命周期由接口javax.servlet.Servlet定义。所有的Java Servlet 必须直接或间接地实现javax.servlet.Servlet接口,这样才能在Servlet Engine上运行。

如何解决servlet线程安全

第一种,继承SingleThreadModel但是这样每次都会创建一个新的servlet实例, 但这样消耗服务器的内存,降低了性能,并且这个接口现在已经过时了,不推荐使用。

第二种:我们尽量避免使用全局变量。

第三种,我们可以通过使用ThreadLocal, 内部结构是一个Map结构,用当前线 程作为key,他会创建多个副本。get,set方法。

第四种,我们当然还可以来加锁,进行解决线程问题。

而且我还知道,向我们这种常用的MVC框架,struts1,spring这些MVC框架,都 是基于servlet发展而来的,就比如struts1 的核心总控制器是ActionServlet, 而springMVC的前端总控制器是dispatchServlet,在项目我们曾经用serlet来生成图片验证码的,防止用户进行暴力破解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值