JAVA WEB一些面试题的个人总结(一)

1、JVM

Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因(先翻译成中间码,即半解释半编译型)。

下图能比较详细的解释JVM的运行机制(转载于https://blog.csdn.net/luomingkui1109/article/details/72820232)。 简单来说,java程序经过一次编译之后,将java代码编译为字节码也就是.class文件,然后在不同的操作系统上依靠不同的java虚拟机进行解释,再转换为不同平台的机器码,最终执行。例如,如果要在windows系统上运行,只需要安装window支持的java虚拟机就行了。

假设有这样一段代码

public class HelloWorld { 
    public static void main(String[] args) 
    { 
        System.out.print("Hello world"); 
    } 
}

下图(转载于https://www.cnblogs.com/leefreeman/p/7344460.html)简单来说,就是java代码通过编译之后生成字节码文件(.class文件),通过:java HelloWorld执行,此时java根据系统版本找到jvm.cfg。通过jvm.cfg文件找到对应的jvm.dll,jvm.dll则是java虚拟机的主要实现。接下来会初始化JVM,并且获取JNI(Java Native Interface)接口,即java本地接口,java被编译成了class文件,JVM通过JNI接口(它还常用于java与操作系统、硬件交互),找到class文件后并装载进JVM,然后找到main方法,最后执行。

image

JVM的内部结构(转载于https://www.cnblogs.com/leefreeman/p/7344460.html)。从这个结构不难看出,class文件被jvm装载以后,经过jvm的内存空间调配,最终是由执行引擎完成class文件的执行。当然这个过程还有其他角色模块的协助,这些模块协同配合才能让一个java程序成功的运行,下面就详细介绍这些模板,它们也是后面学习jvm最重要的部分。

image

内存空间:

JVM内存空间包含:方法区、java堆、java栈、本地方法栈。

方法区是各个线程共享的区域,存放类信息、常量、静态变量。

java堆也是线程共享的区域,我们的类的实例就放在这个区域,可以想象你的一个系统会产生很多实例,因此java堆的空间也是最大的。如果java堆空间不足了,程序会抛出OutOfMemoryError异常。

java栈是每个线程私有的区域,它的生命周期与线程相同,一个线程对应一个java栈,每执行一个方法就会往栈中压入一个元素,这个元素叫“栈帧”,而栈帧中包括了方法中的局部变量、用于存放中间状态值的操作栈,这里面有很多细节,我们以后再讲。如果java栈空间不足了,程序会抛出StackOverflowError异常,想一想什么情况下会容易产生这个错误,对,递归,递归如果深度很深,就会执行大量的方法,方法越多java栈的占用空间越大。

本地方法栈角色和java栈类似,只不过它是用来表示执行本地方法的,本地方法栈存放的方法调用本地方法接口,最终调用本地方法库,实现与操作系统、硬件交互的目的。

PC寄存器,说到这里我们的类已经加载了,实例对象、方法、静态变量都去了自己改去的地方,那么问题来了,程序该怎么执行,哪个方法先执行,哪个方法后执行,这些指令执行的顺序就是PC寄存器在管,它的作用就是控制程序指令的执行顺序。

执行引擎当然就是根据PC寄存器调配的指令顺序,依次执行程序指令。

2、集合的实现

Java的集合类主要有Set, List, Map, Queue。

Set特点:无序,不重复。

List特点:有序,可重复。

Map特点:具有映射关系,可以通过“键”找到“值”。

Queue特点:Java5新增,队列集合,先进先出顺序。

数组可以保存基本数据类型或对象,集合只能保存对象,基本数据类型的变量要转换成对应的包装类才能放入集合类中。

基本概念

在编程中,常常需要集中存放多个数据。从传统意义上讲,数组是我们的一个很好的选择,前提是我们事先已经明确知道我们将要保存的对象的数量。一旦在数组初始化时指定了这个数组长度,这个数组长度就是不可变的,如果我们需要保存一个可以动态增长的数据(在编译时无法确定具体的数量),java的集合类就是一个很好的设计方案了。

集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。所以的集合类都位于java.util包下,后来为了处理多线程环境下的并发安全问题,java5还在java.util.concurrent包下提供了一些多线程支持的集合类。

在学习Java中的集合类的API、编程原理的时候,我们一定要明白,"集合"是一个很古老的数学概念,它远远早于Java的出现。从数学概念的角度来理解集合能帮助我们更好的理解编程中什么时候该使用什么类型的集合类。

集合类间继承关系:

collection(单列集合):

 List(有序,可重复)
            ArrayList
                底层数据结构是数组,查询快,增删慢
                线程不安全,效率高
            Vector
                底层数据结构是数组,查询快,增删慢
                线程安全,效率低
            LinkedList
                底层数据结构是链表,查询慢,增删快
                线程不安全,效率高

Set(无序,唯一)
            HashSet
                底层数据结构是哈希表。
                哈希表依赖两个方法:hashCode()和equals()

                执行顺序:
                    首先判断hashCode()值是否相同
                        是:继续执行equals(),看其返回值
                            是true:说明元素重复,不添加
                            是false:就直接添加到集合
                        否:就直接添加到集合
                最终:
                    自动生成hashCode()和equals()即可
                    
                LinkedHashSet
                    底层数据结构由链表和哈希表组成。
                    由链表保证元素有序。
                    由哈希表保证元素唯一。
            TreeSet
                底层数据结构是红黑树。(是一种自平衡的二叉树)
                如何保证元素唯一性呢?
                    根据比较的返回值是否是0来决定
                如何保证元素的排序呢?
                    两种方式
                        自然排序(元素具备比较性)
                            让元素所属的类实现Comparable接口
                        比较器排序(集合具备比较性)
                            让集合接收一个Comparator的实现类对象

Map实现类用于保存具有映射关系的数据。Map保存的每项数据都是key-value对,也就是由key和value两个值组成。Map里的key是不可重复的,key用户标识集合里的每项数据。

Map(双列集合):

        A:Map集合的数据结构仅仅针对键有效,与值无关。
        B:存储的是键值对形式的元素,键唯一,值可重复。
        
        HashMap
            底层数据结构是哈希表。线程不安全,效率高
                哈希表依赖两个方法:hashCode()和equals()
                执行顺序:
                    首先判断hashCode()值是否相同
                        是:继续执行equals(),看其返回值
                            是true:说明元素重复,不添加
                            是false:就直接添加到集合
                        否:就直接添加到集合
                最终:
                    自动生成hashCode()和equals()即可
            LinkedHashMap
                底层数据结构由链表和哈希表组成。
                    由链表保证元素有序。
                    由哈希表保证元素唯一。
        Hashtable
            底层数据结构是哈希表。线程安全,效率低
                哈希表依赖两个方法:hashCode()和equals()
                执行顺序:
                    首先判断hashCode()值是否相同
                        是:继续执行equals(),看其返回值
                            是true:说明元素重复,不添加
                            是false:就直接添加到集合
                        否:就直接添加到集合
                最终:
                    自动生成hashCode()和equals()即可
        TreeMap
            底层数据结构是红黑树。(是一种自平衡的二叉树)
                如何保证元素唯一性呢?
                    根据比较的返回值是否是0来决定
                如何保证元素的排序呢?
                    两种方式
                        自然排序(元素具备比较性)
                            让元素所属的类实现Comparable接口
                        比较器排序(集合具备比较性)
                            让集合接收一个Comparator的实现类对象

collection和map是Java集合类的根接口。

3、HashMap和HashTable的区别

HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度

HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。 HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。 HashMap不能保证随着时间的推移Map中的元素次序是不变的。

结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构。 

HashMap可以通过下面的语句进行同步: 

Map m = Collections.synchronizeMap(hashMap); 

Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java 5或以上的话,请使用ConcurrentHashMap吧。

1、是由不同作者写的。

2、继承的父类不同:HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口 。

3、 对外提供的接口不同 :Hashtable比HashMap多提供了elments() 和contains() 两个方法。elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。

4、Hashtable既不支持Null key也不支持Null value。Hashtable的put()方法的注释中有说明。 

è¿éåå¾çæè¿°

5、遍历方式的内部实现上不同 ,Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。

6、初始容量大小和每次扩充容量大小的不同 ,Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。

创建时,如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说Hashtable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。

之所以会有这样的不同,是因为Hashtable和HashMap设计时的侧重点不同。Hashtable的侧重点是哈希的结果更加均匀,使得哈希冲突减少。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。而HashMap则更加关注hash的计算效率问题。在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改动。这从而导致了Hashtable和HashMap的计算hash值的方法不同。 

7、计算hash值的方法不同 。为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。Hashtable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。 
 

4、JDK1.7前后HashMap源码的区别

JDK1.8以后 HashMap的数据结构发生了一些改变,从单纯的数组加链表结构变成数组+链表+红黑树。

HashMap

其中Node是HashMap的一个内部类,实现Map.Entry接口,本质是一个KV映射,上图中每个元素都是一个Node对象. 
HashMap顾名思义是通过Hash表进行存储.为了解决哈希碰撞的问题,Java采用这种数组 + 链表方式来进行存储. 
具体的put方法源码如下.

HashMapçputæ¹æ³

未完待续

6、设计模式中的单例模式

是一种常用的软件设计模式,在它的核心结构中值包含一个被称为单例的特殊类。一个类只有一个实例,即一个类只有一个对象实例。

7、工厂

8、Spring(服务端方向)

9、IOC和AOP

10、简述dubbo

è¿éåå¾çæè¿°

Provider:暴露服务的服务提供方。

Consumer: 调用远程服务的服务消费方。

Registry:服务注册与发现的注册中心。

Monitor: 统计服务的调用次调和调用时间的监控中心。

Container: 服务运行容器。

dubbo是一个分布式的服务框架,致力于提高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。

简言之,dubbo就是一个服务框架,如果没有分布式的需求,其实不需要用的,只有分布式的时候,才需要dubbo这样的分布式框架

dubbo的核心:

1)远程通讯:提供多种基于长连接的NIO框架封装,包括多线程模型,序列号,以及“请求-响应”模式的信息交换方式

2)集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持

3)自动发现:基于注册中心目录服务,使服务消费方能够动态查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器

11、redis相关

1、redis简介

一款内存高速缓存数据库。Redis全称为:Remote Dictionary Server(远程数据服务),该软件使用C语言编写,Redis是一个key-value存储系统,它支持丰富的数据类型,如:string、list、set、zset(sorted set)、hash。

2、优点

读写效率高;

支持主从模式,可以配置集群,这样更利于支撑起大型的项目。

3、使用redis的原因

主要是考虑到性能和并发。(其他还有分布锁等等)

性能:碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。

并发:在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。

4、缺点

(一)缓存和数据库双写一致性问题

(二)缓存雪崩问题

(三)缓存击穿问题

(四)缓存的并发竞争问题

5、redis内部机制考察之一

redis是单线程工作模式,并且单线程的redis极快,原因主要有以下:

(一)纯内存操作
(二)单线程操作,避免了频繁的上下文切换
(三)采用了非阻塞I/O多路复用机制

6、redis常用数据类型解析

常用的有5种。

(一)String

常规的set/get操作,value可以是String也可以是数字。一般做复杂计数功能缓存。

(二)hash

这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。

  (三)list
使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适---取行情信息。就也是个生产者和消费者的场景。LIST可以很好的完成排队,先进先出的原则。
  (四)set
因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。


另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
(五)sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。

12、多线程方面

多线程实现4方式

1.继承Thread类,重写run方法

2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target

3.通过Callable和FutureTask创建线程

4.通过线程池创建线程

JNI 创建线程(附加)

前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果 
后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中

多线程不是为了提高执行速度,而是提高应用程序的使用率.

线程和线程共享”堆内存和方法区内存”.栈内存是独立的,一个线程一个栈.

线程可分为用户线程和守护线程。

未完待续

13、线程池的源码

16、spring-boot

帮助开发者快速搭建spring框架。

优点:Spring Boot使编码变简单,Spring Boot使配置变简单,Spring Boot使部署变简单, Spring Boot使监控变简单。

不足:

17、spring cloud和dubbo区别

18、dubbo和SOA架构

19、数据库

20、数据库索引

以上是一些java web面试题的个人总结,如有错误欢迎指正。暂时先写这么多,答案和其他题目慢慢补充。

猜你喜欢

转载自blog.csdn.net/weixin_39738307/article/details/82050305