招银网络Java面经

目录

 

一面

string判等

Java集合的类层次关系,集合容器介绍

给出2个kv实例,描述hashmap.put()过程

任何自定义类都能放入hashmap吗?有什么要求?如何实现?

常见的索引结构

mysql主键和其他索引的区别

谈一谈你对微服务的理解。

Spring的AOP

拦截器用来做什么?哪些场景需要用拦截器?

什么是java内存泄漏?举几个例子。

static方法在哪个内存区域

写代码,使2个线程出现死锁情况

写代码,判断链表是否有环

集群如何管理session


一面

string判等

“==”判断符号左右两个变量(引用类型)是否指向同一内存地址。

equals()方法判断两个对象是否一样(所有成员的值是否相同)

例1:


String a = "abc";
String b = "abc";
System.out.println(a == b);	//true
System.out.println(a.equals(b));	//true
在例1中,“abc”是放在常量池(constant pool)中的,所以,虽然a,b都等于“abc”,但是内存中只有一份副本,所以“==”的结果为true
例2:


String a = new String("abc");
String b = new String("abc");
System.out.println(a == b);	//false
System.out.println(a.equals(b));	//true
在例2中,new方法决定了两个不同的string "abc"被创建放在了内存heap区(堆上),分别被a和b所指向,因此“==”返回了false

Java集合的类层次关系,集合容器介绍

collection和collections的区别:collection是集合类(collection)的顶级接口,collections是提供了一系列静态方法的集合工具类。

collection的类层次结构:包括set、list和queue接口。其下分别有HashSet,LinkedHasSet,TreeSet;ArrayList,Vector,LinkedList;PriorityQueue。

Map的类层次结构:包括Hashtable,LinkedHashMap,HashMap和TreeMap。

给出2个kv实例,描述hashmap.put()过程

HashMap<K,V>是最常用的一种map,在其内部包装了一个Node<K,V>的类,并且用Node型的数组table来存储数据,与ArrayList一样的实现了元素的增删以及扩容等功能。K一般是8种基本类型的封装类和String类,可存储null的键和null的值。

put方法将<K,V>放在map中。如果该key已经存放于map中,则用新值直接替换旧值。

put的返回值:如果该key已经存放在map中,则返回其映射的旧值;如果不存在,则返回null,表示没有该key对应的映射。(也有可能原来的映射是key-null)

当new HashMap实例时并没有初始化其成员变量transientt Node<K,V>[] table,也就是说并没有为table分配内存。只有当put元素时才通过resize方法对table进行初始化。

put方法分为两种情况,bucket是以链表形式存储的还是以树形结构存储的。如果是key已存在则修改旧值并返回旧值;如果key不存在就执行插入操作并返回null。如果是插入操作还要modCount++。当如果是链表存储时,如果插入元素之后超过了TREEIFY_THRSHOLD,还要进行树化操作。

注意:put操作,当发生碰撞时,如果是使用链表处理冲突,执行的是尾插法。这个和ConcurrentHashMap不同,ConcurrentHashMap使用的是头插法。因为其HashEntry的next是final的。

put操作的基本流程:

1、通过hash值得到所在bucket的下标,如果是null表示没有发生碰撞,直接put;

2、如果发生了碰撞,则解决发生碰撞的实现方式:链表还是树;

3、如果找到该key的节点则执行更新操作,无需对modCount增一;

4、如果没有找到该key的节点,则执行插入操作,并对modCount增一;

5、在执行插入操作时,如果bucket中bin的数量超过TREEIFY_THRESHOLD,则要树化。

6、在执行插入操作之后,如果size超过了threshold则要扩容。

注:https://xiao1zhao2.iteye.com/blog/2198509

任何自定义类都能放入hashmap吗?有什么要求?如何实现?

以下是Object对象API关于equal方法和hashCode方法的说明:

1。If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result. 
2。It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables. 
以上API说明是对之前2点的官方详细说明 
关于第一点,相等(相同)的对象必须具有相等的哈希码(或者散列码),为什么?

想象一下,假如两个Java对象A和B,A和B相等(eqauls结果为true),但A和B的哈希码不同,则A和B存入HashMap时的哈希码计算得到的HashMap内部数组位置索引可能不同,那么A和B很有可能允许同时存入HashMap,显然相等/相同的元素是不允许同时存入HashMap,HashMap不允许存放重复元素。

关于第二点,两个对象的hashCode相同,它们并不一定相同

也就是说,不同对象的hashCode可能相同;假如两个Java对象A和B,A和B不相等(eqauls结果为false),但A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同这时HashMap会在该位置建立一个链接表,将A和B串起来放在该位置,显然,该情况不违反HashMap的使用原则,是允许的。当然,哈希冲突越少越好,尽量采用好的哈希算法以避免哈希冲突。

Hashset是继承Set接口,Set接口又实现Collection接口,这是层次关系。那么Hashset、Hashmap、Hashtable中的存储操作是根据什么原理来存取对象的呢?

下面以HashSet为例进行分析,我们都知道:在hashset中不允许出现重复对象,元素的位置也是不确定的。在hashset中又是怎样判定元素是否重复的呢?在java的集合中,判断两个对象是否相等的规则是:

1.判断两个对象的hashCode是否相等, 如果不相等,认为两个对象也不相等,完毕 
如果相等,转入2(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。) 
2.判断两个对象用equals运算是否相等,如果不相等,认为两个对象也不相等;如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键) 
为什么是两条准则,难道用第一条不行吗?不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,所以必须用第2条准则进行限制,才能保证加入的为非重复元素。
原文:https://blog.csdn.net/achiberx/article/details/73655737 

常见的索引结构

B-树                                                                                                                                                                                                                        B-树结构支持插入、控制操作以及通过管理一系列树根状结构的彼此联通的节点中来做选择。B-树结构中有两种节点类型:索引节点和叶子节点。叶子节点是存储数据的,而索引节点是用来告诉用户存储在叶子节点中的数据的顺序,并帮助用户找到数据。B-树不是二叉树,二叉树只是一种简单的节点层次结构的实现。

B+树                                                                                                                                                                                                                       B+树是B-树结构的增强版,尽管B+树支持B-树的所有特性,他们之间最显著的不同点在于B+树中底层数据是按照提及的索引列进行排序的。B+树还通过在叶子节点之间附加引用来优化扫描的性能。

散列表
散列表数据结构是一个简单的概念,他将一种算法应用到给定值中以在底层数据存储系统中返回一个唯一的指针或位置。散列表的优点是始终以线性时间复杂度找到需要读取的行的位置,而不想B-树那样需要跨越多层节点来确定位置。

索引不能为空会导致索引失效

mysql主键和其他索引的区别

1、主键一定是唯一性索引,唯一性索引不一定是主键(主键就是能够唯一标识表中某一行的属性或者是属性组,一个表只能有一个主键,但可以有多个候选索引。因为主键可以唯一标识一行记录,所以可以确保执行数据更新、删除的时候不会出现错误的。主键还经常和外键构成参照完整性约束,防止出现数据不一致。数据库管理系统对于主键自动生成唯一索引,所以主键也是一个特殊的索引)

2、一个表中可以有多个唯一索引,但是主键只能有一个

3、主键列不能为空值,而唯一性索引列允许空值;

4、主键也可以由多个字段组成组成复合主键,同时主键也是唯一索引;

5、唯一索引表示索引值唯一,可以由一个或者几个字段组成,一个表可以有多个唯一索引

谈一谈你对微服务的理解。

      在介绍微服务时,首先得先理解什么是微服务,顾名思义,微服务得从两个方面去理解,什么是"微"、什么是"服务", 微 狭义来讲就是体积小、著名的"2 pizza 团队"很好的诠释了这一解释(2 pizza 团队最早是亚马逊 CEO Bezos提出来的,意思是说单个服务的设计,所有参与人从设计、开发、测试、运维所有人加起来 只需要2个披萨就够了 )。 而所谓服务,一定要区别于系统,服务一个或者一组相对较小且独立的功能单元,是用户可以感知最小功能集。

https://blog.csdn.net/wuxiaobingandbob/article/details/78642020

Spring的AOP

AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志、事务、权限等待。structs2的拦截器涉及就是基于AOP的思想,是个比较经典的例子。

一、AOP基本概念:

1、Aspect(切面):通常是一个类,里面可以定义切入点和通知。

2、JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。

3、Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around

4、Pointcut(切入点):带有通知的连接点,在程序中主要体现为书写切入点表达式;

5、AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态爱代理,也可以是CGLIB代理,前者基于接口,后者基于子类。

二、Spring AOP

Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关旭都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。

三 基于注解的AOP配置方式

1.启用@AsjectJ支持

在applicationContext.xml中配置下面一句:

<aop:aspectj-autoproxy />

2.通知类型介绍

(1)Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可

(2)AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值

(3)AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名

来访问目标方法中所抛出的异常对象

(4)After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式

(5)Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint

拦截器用来做什么?哪些场景需要用拦截器?

拦截器可以说相当于是个过滤器:就是把不想要的或不想显示的内容给过滤掉。拦截器可以抽象出一部分代码用来完善原来的action。同时可以减轻代码冗余,提高重用率。

比如在登入一个页面时,如果要求用户密码、权限等的验证,就可以用自定义的拦截器进行密码验证和权限限制。对符合的登入者才跳转到正确页面。这样如果有新增权限的话,不用在action里修改任何代码,直接在interceptor里修改就行了。

好处:拦截器也可以让你将通用的代码模块化并作为可重用的类。Structs2中的很多特性都是由拦截器来完成的。

作用:可以构成拦截器栈,完成特定功能。比如日志记录、登录判断、权限检查等作用。

什么是java内存泄漏?举几个例子。

Java内存泄漏引起的原因:

  内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。

  长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。

造成内存泄漏的几种情况:

1、静态集合类引起内存泄漏

  像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。

2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。

3、监听器

  在释放对象的时候却没有去删除这些监听器,增加了内存泄漏的机会。

4、各种连接

  比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。

5、内部类和外部模块的引用

  内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: public void registerMsg(Object b); 这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。

6、单例模式

  不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。

static方法在哪个内存区域

JVM内存总体一共分为了 4个部分:
stack segment、 
heap segment、 
code segment、 
data segment)

stack segment(栈):
局部变量:如main函数中声明的str变量。如图中,str,t存在于stack区:

栈中保存基本数据类型的变量和自定义的对象的引用(不是对象),对象本身都存放在堆区中,被执行的方法的也是pull到栈中,当方法执行完后再push出栈。

heap segment(堆)
当new 一个对象的时候,此对象放在了heap segment(堆)当中。t存放在stack中,而new Test()这个实实在在的对象是存在了heap中 
如。代码:

heap中存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)

code segment(代码区)
类中方法的话,是存在在 code segment(代码区)中了

data segment(数据区)
static 的变量或者字符串常量存在数据区

static变量与非static变量
static的变量与非static变量存放位置不一样,并且变量的访问权限也不一样。

static变量是全局的,是类的所有对象都能访问的,是所有方法都可以访问的,无论是static修饰的方法还是非static方法都可以访问,没有限制。

而非static变量是私有的,是有访问限制的,就是说是每个对象独有的特有的,并且只有非static方法才可以访问。

如图: 
 
static方法只能访问static的变量,没有权限访问非static变量。

 
static方法中声明的变量可以与非static变量并且是类的属性重名

 
方法中声明的变量可以与非static变量并且是类的属性重名

这是由于类的属性是存在与对象中的,是在heap中,而方法中的变量是存在与code 区中的,在不同的区中可以重名。

 
非static方法中可以方访问static变量。

这时因为static变量是共享的,任何方法,任何对象都可以访问

 
static方法可以访问static属性

static方法中声明的变量name存在code区,而类的属性中的name存在对象中,而对象存在于heap区。所以不会报错。

写代码,使2个线程出现死锁情况

public class DeadlockExample {
	String resource1 = "资源1";
	String resource2 = "资源2";
	Thread t1 = new Thread("线程1") {
		public void run() {
			while (true) {
				synchronized (resource1) {
					synchronized (resource2) {
						System.out.printf("线程1拥有[%s], 需要[%s]\n", resource1, resource2);
					}
				}
			}
		}
	};
	Thread t2 = new Thread("线程2") {
		public void run() {
			while (true) {
				synchronized (resource2) {
					synchronized (resource1) {
						System.out.printf("线程2拥有[%s], 需要[%s]\n", resource2, resource1);
					}
				}
			}
		}
	};
	public static void main(String a[]) {
		DeadlockExample test = new DeadlockExample();
		test.t1.start();
		test.t2.start();
	}
}

写代码,判断链表是否有环

思路: 
1. 设置两个指针,一个指针每次前进一步,一个指针每次前进两步,如果它们中有一个先到达链表末尾,那么无环,否则它们一定会相遇。 
2. 相遇后,每次前进一步的指针继续前进,同时另设一个指针从链表头部开始每次前进一步,它们必然会相遇于环的入口。

public class Test{
    

    public static class LinkedList{
        int data;
        LinkedList next;
        
        public LinkedList(int data){
            this.data = data;
            this.next = null;
        }
    }

    public static LinkedList isCyclicLinkedList(LinkedList head){
        if(head == null)
            return null;
        LinkedList pOne = head;
        LinkedList pTwo = head;
        if(pTwo.next != null)
            pTwo = pTwo.next.next;
        else
            return null;
        pOne = pOne.next;

        while(pTwo != pOne){
            if(pTwo != null&&pTwo.next != null){
                pTwo = pTwo.next.next;
            }else{
                return null;
            }
            pOne = pOne.next;
        }
        while(head != pOne){
            pOne = pOne.next;
            head = head.next;
        }
        return pOne;
    }
}

集群如何管理session

用户使用网站的服务,基本上需要浏览器与Web 服务器的多次交互。HTTP 协议本身是无状态的,需要基于HTTP 协议支持会话状态(Session State)的机制。而这样的机制应该可以使Web 服务器从多次单独的HTTP 请求中看到“会话”,也就是知道哪些请求是来自哪个会话的。具体实现方式为:在会话开始时,分配一个唯一的会话标识(SessionId),通过Cookie 把这个标识告诉浏览器,以后每次请求的时候,浏览器都会带上这个会话标识来告诉Web 服务器请求是属于哪个会话的。在Web 服务器上,各个会话有独立的存储,保存不同会话的信息。如果遇到禁用Cookie 的情况,一般的做法就是把这个会话标识放到URL 的参数中。我们可以通过图2-8 来看一下上述过程。

当我们的应用服务器从一台变到两台后,如同图2-7 中的结构,我们就会遇到Session的问题了。具体是指什么问题呢?

我们来看图2-9,当一个带有会话标识的HTTP 请求到了Web 服务器后,需要在HTTP请求的处理过程中找到对应的会话数据(Session)。而问题就在于,会话数据是需要保存在单机上的。

在图2-9 所示的网站中,如果我第一次访问网站时请求落到了左边的服务器,那么我的Session 就创建在左边的服务器上了,如果我们不做处理,就不能保证接下来的请求每次都落在同一边的服务器上了,这就是Session 问题。

我们看看这个问题的几种解决方案。

1.Session Sticky

在单机的情况下,会话保存在单机上,请求也都是由这个机器处理,所以不会有问题。Web 服务器变成多台以后,如果保证同一个会话的请求都在同一个Web 服务器上处理,那么对这个会话的个体来说,与之前单机的情况是一样的。

如果要做到这样,就需要负载均衡器能够根据每次请求的会话标识来进行请求转发,如图2-10 所示,称为Session Sticky 方式。 

这个方案本身非常简单,对于Web 服务器来说,该方案和单机的情况是一样的,只是我们在负载均衡器上做了“手脚”。这个方案可以让同样Session 的请求每次都发送到同一个服务器端处理,非常利于针对Session 进行服务器端本地的缓存。不过也带来了如下几个问题:

如果有一台Web 服务器宕机或者重启,那么这台机器上的会话数据会丢失。如果会话中有登录状态数据,那么用户就要重新登录了。

会话标识是应用层的信息,那么负载均衡器要将同一个会话的请求都保存到同一个Web服务器上的话,就需要进行应用层(第7 层)的解析,这个开销比第4 层的交换要大。负载均衡器变为了一个有状态的节点,要将会话保存到具体Web 服务器的映射。和无状态的节点相比,内存消耗会更大,容灾方面会更麻烦。

这种方式我们称为Session Sticky。打个比方来说,如果说Web 服务器是我们每次吃饭的饭店,会话数据就是我们吃饭用的碗筷。要保证每次吃饭都用自己的碗筷的话,我就把餐具存在某一家,并且每次都去这家店吃,是个不错的主意。

2.Session Replication

如果我们继续以去饭店吃饭类比,那么除了前面的方式之外,如果我在每个店里都存放一套自己的餐具,不就可以更加自由地选择饭店了吗?Session Replication 就是这样的一种方式,这一点从字面上也很容易看出来。

先看一下图2-11,如下。 

可以看到,在Session Replication 方式中,不再要求负载均衡器来保证同一个会话的多次请求必须到同一个Web 服务器上了。而我们的Web 服务器之间则增加了会话数据的同步。通过同步就保证了不同Web 服务器之间的Session 数据的一致。就如同每家饭店都有我的碗筷,我就能随便选择去哪家吃饭了。

一般的应用容器都支持(包括了商业的和开源的)Session Replication 方式,与Session Sticky 方案相比,Session Replication 方式对负载均衡器没有那么多的要求。不过这个方案本身也有问题,而且在一些场景下,问题非常严重。我们来看一下这些问题。

同步Session 数据造成了网络带宽的开销。只要Session 数据有变化,就需要将数据同步到所有其他机器上,机器数越多,同步带来的网络带宽开销就越大。

每台Web 服务器都要保存所有的Session 数据,如果整个集群的Session 数很多(很多人在同时访问网站)的话,每台机器用于保存Session 数据的内容占用会很严重。这就是Session Replication 方案。这个方案是靠应用容器来完成Session 的复制从而使得应用解决Session 问题的,应用本身并不用关心这个事情。不过,这个方案不适合集群机器数多的场景。如果只有几台机器,用这个方案是可以的。

3.Session 数据集中存储

同样是希望同一个会话的请求可以发到不同的Web 服务器上,刚才的Session Replication是一种方案,还有另一种方案就是把Session 数据集中存储起来,然后不同Web 服务器从同样的地方来获取Session。大概的结构如图2-12 所示。

可以看到,与Session Replication 方案一样的部分是,会话请求经过负载均衡器后,不会被固定在同样的Web 服务器上。不同的地方是,Web 服务器之间没有了Session 数据复制,并且Session 数据也不是保存在本机了,而是放在了另一个集中存储的地方。这样,不论是哪台Web 服务器,也不论修改的是哪个Session 数据,最终的修改都发生在这个集中存储的地方,而Web 服务器使用Session 数据时,也是从这个集中存储Session 数据的地方来读取。这样的方式保证了不同Web 服务器上读到的Session 数据都是一样的。而存储Session 数据的具体方式,可以使用数据库,也可以使用其他分布式存储系统。这个方案解决了SessionReplication 方案中内存的问题,而对于网络带宽,这个方案也比Session Replication 要好。该方案存在的问题是什么呢?

读写Session 数据引入了网络操作,这相对于本机的数据读取来说,问题就在于存在时延和不稳定性,不过我们的通信基本都是发生在内网,问题不大。

如果集中存储Session 的机器或者集群有问题,就会影响我们的应用。

相对于Session Replication,当Web 服务器数量比较大、Session 数比较多的时候,这个集中存储方案的优势是非常明显的。

4.Cookie Based

Cookie Based 方案是要介绍的最后一个解决Session 问题的方案。这个方案对于同一个会话的不同请求也是不限制具体处理机器的。和Session Replication 以及Session 数据集中管理的方案不同,这个方案是通过Cookie 来传递Session 数据的。还是先看看下面的图2-13吧。

从图2-13 可以看到,我们的Session 数据放在Cookie 中,然后在Web 服务器上从Cookie中生成对应的Session 数据。这就好比我每次都把自己的碗筷带在身上,这样我去哪家饭店吃饭就可以随意选择了。相对于前面的集中存储,这个方案不会依赖外部的一个存储系统,也就不存在从外部系统获取、写入Session 数据的网络时延、不稳定性了。不过,这个方案依然存在不足:

Cookie 长度的限制。我们知道Cookie 是有长度限制的,而这也会限制Session 数据的长度。

安全性。Session 数据本来都是服务端数据,而这个方案是让这些服务端数据到了外部网络及客户端,因此存在安全性上的问题。我们可以对写入Cookie 的Session 数据做加密,不过对于安全来说,物理上不能接触才是安全的。

带宽消耗。这里指的不是内部Web 服务器之间的带宽消耗,而是我们数据中心的整体外部带宽的消耗。

性能影响。每次HTTP 请求和响应都带有Session 数据,对Web 服务器来说,在同样的处理情况下,响应的结果输出越少,支持的并发请求就会越多。

猜你喜欢

转载自blog.csdn.net/strawqqhat/article/details/88093942