备份1

一、Java基础

1、String字符串

1.1、下面这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d" ?

答:由前面的两道题,其实我们已经知道这道题的答案了,明白了它的原理也就不那么难了,答案就是0个或者1个

因为如果对象"a"+"b"+"c"+"d"在编译的时候会自动将其编译成"abcd",所以这道题就变成了String s="abcd";创建了几个对象?  

如果在执行该句代码之前缓冲区中存在"abcd"这个对象,那么就不会重新创建对象,那么就是0个,如果缓冲区中没有"abcd"这个对象,那么就是1个。

1.2、String s=new String("xyz");创建了几个对象?有什么不同?

答:创建了一个或者两个 ,首先new String("xyz")肯定会在内存中创建一个对象,它是参照常量"xyz"进行创建对象,所以首先会看缓冲区中是否存在常

量"xyz",如果不存在,要先在缓冲区中创建一个常量"xyz",否则不用创建,所以答案为1个或者2个。

1.3、==和equals的区别

(1)、基本数据类型,也称为原始数据类型(byte、char、short、int、long、float、double、boolean)它们之间的比较用双等号(==),比较的是它们的值。

(2)、对象的比较,当对象用==进行比较的时候比较的是它们的内存中的存放地址,所以,new出的两个相同对象用==比较是不相等的,因为它们在内存中存放的地址不同,所以对象的比较要用equals进行比较,的equals方法是用来比较两个字符串的内容是否相同的,而不是内存中的存放地址。

1.4、StringBuffer和StringBuilder的区别

答:区别就是StringBuffer是支持多线程操作,是线程安全的,而StringBuilder只能支持单线程操作,是线程不安全的。由于StringBuffer支持了多线程的操作导

致比StringBuilder性能慢。

2、Java集合类

2.1、Collection和Collections的差别

答:java.util.Collection 是一个集合接口,Collection接口在Java类库中有非常多详细的实现。比如List、Set

java.util.Collections 是针对集合类的一个帮助类,它提供了一系列的静态方法实现对各种集合的搜索、排序、线程安全化等操作。

2.2、ArrayList与Vector的差别

答:这两个类都实现了List接口(List接口继承自Collection接口)。它们都是有序集合。它们内部的元素都是能够反复的,都能够依据序号取出当中的某一元素。

它们两个的差别在于:

(1)、线程安全的问题:Vector是早期Java就有的,是同意多线程操作的。是线程安全的;而ArrayList是在Java2中才出现,它是线程不安全的,仅仅能使用单线程

操作。 因为Vector支持多线程操作,所以在性能上就比不上ArrayList了。

相同的HashTable相比于HashMap也是支持多线程的操作而导致性能不如HashMap。

(2)、数据增长的问题

ArrayList和Vector都有一个初始的容量大小,当存储进去它们里面的元素个数超出容量的时候。就须要添加ArrayList和Vector的存储空间,每次添加存储空间

的时候不是仅仅添加一个存储单元。是添加多个存储单元。

Vector默认添加原来的一倍,ArrayList默认添加原来的0.5倍。

Vector能够由我们自己来设置增长的大小,ArrayList没有提供相关的方法。

2.3、LinkedList与ArrayList有什么差别

(1)、ArrayList是基于动态数组实现的,LinkedList是基于链表的数据结构。

(2)、get訪问List内部随意元素时。ArrayList的性能要比LinkedList性能好。LinkedList中的get方法是要依照顺序从列表的一端開始检查,直到还有一端

(3)、对于新增和删除操作LinkedList要强于ArrayList。由于ArrayList要移动数据

2.4、HashMap

2.4.1、你知道HashMap的工作原理吗?” “你知道HashMap的get()方法的工作原理吗?

答:HashMap是基于hashing的,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket(桶)位置来储存Entry对象。”这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Entry。

2.4.2、当两个对象的hashcode相同会发生什么?

答:因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用LinkedList存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在LinkedList中。(当向 HashMap 中添加 key-value 对,由其 key 的 hashCode() 返回值决定该 key-value 对(就是 Entry 对象)的存储位置。当两个 Entry 对象的 key 的 hashCode() 返回值相同时,将由 key 通过 eqauls() 比较值决定是采用覆盖行为(返回 true),还是产生 Entry 链(返回 false)。

2.4.3、如果两个键的hashcode相同,你如何获取值对象?

答:当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后获取值对象。如果有两个值对象储存在同一个bucket,将会遍历LinkedList直到找到值对象。找到bucket位置之后,会调用keys.equals()方法去找到LinkedList中正确的节点,最终找到要找的值对象。(当程序通过 key 取出对应 value 时,系统只要先计算出该 key 的 hashCode() 返回值,在根据该 hashCode 返回值找出该 key 在 table 数组中的索引,然后取出该索引处的 Entry,最后返回该 key 对应的 value 即可。

2.4.4、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

答:当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

2.4.5、你了解重新调整HashMap大小存在什么问题吗?

答:当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在LinkedList中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在LinkedList的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。这个时候,你可以质问面试官,为什么这么奇怪,要在多线程的环境下使用HashMap呢?

 

2.4.6、你知道get和put的原理吗?equals()和hashCode()的都有什么作用?
通过对key的hashCode()进行hashing,并计算下标,从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点

2.4.7、你知道hash的实现吗?为什么要这样实现?
hash是通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以保证高低bit都参与到hash的计算中,同时不会有太大的开销。

2.5、ConcurrentHashMap

2.5.1、ConcurrentHashMap原理

答:ConcurrentHashMap,采用锁分段技术实现线程安全,主要有三大结构:整个Hash表,segment(段),HashEntry(节点)。每个segment就相当于一个HashTable

(1) 、HashEntry(节点)

每个HashEntry代表Hash表中的一个节点,在其定义的结构中可以看到,除了value值没有定义final,其余的都定义为final类型,这就意味着我们删除或者增加一个节点的时候,就必须从头开始重新建立Hash链

(2) 、segment(段)

Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。每个 Segment 对象用来守护其包含的若干个桶,每个 Segment 对象中包含一个计数器,而不是在 ConcurrentHashMap 中使用全局的计数器,是为了避免出现“热点域”而影响 ConcurrentHashMap 的并发性,默认的情况下,每个ConcurrentHashMap 类会创建16个并发的segment。

3、多线程

3.1、在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?

答:Synchronized 只是jvm里面自己的一个协议,Lock则是基于JDK的一个接口,Lock接口最大的优势是为读和写分别提供了锁,比如ReentrantReadWriteLock.ReadLock类能够获取读取锁,ReentrantReadWriteLock.WriteLock类能够获取写入锁。相比较而言,Synchronized使用比较简单,我们不需要手动释放锁,但缺点是使用不太灵活,比如我比较熟悉的Lock锁的实现类ReentrantLock,在使用时就可以人为的使用中断锁,这个要注意的是中断锁不能使用lock()方法加锁,而应该使用lockInterruptibly()以便在遇到所中断时能优先响应中断请求。同时我们还能手动指定公平锁,即尽量以请求锁的顺序来获取锁,以满足业务需求.

实现方式:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
 * @author EX_KJKFB_GONGLI
 *
 */
public class TestReentrantReadWriteLock {
 public static void main(String[] args){
  final DoThread doThread = new DoThread();
  for(int i=0;i<4;i++){
   new Thread(new Runnable(){
    @Override
    public void run() {
     // TODO Auto-generated method stub
     doThread.read();
    }}).start();
  }
  for(int i=0;i<4;i++){
   new Thread(new Runnable(){
    @Override
    public void run() {
     // TODO Auto-generated method stub
     doThread.write();
    }}).start();
  }
 }
}

class DoThread {
 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
 ReadWriteLock rwLock = new ReentrantReadWriteLock();
 /**
  * 多个线程读
  */
 public void read(){
  rwLock.readLock().lock();//读锁开启,读进程均可进入
  try {
   System.out.println(Thread.currentThread().getName()+"read lock is ready.."+sdf.format(new Date()));
  } catch (Exception e) {
   // TODO: handle exception
   e.printStackTrace();
  } finally {
   rwLock.readLock().unlock();
  }   
 }
 
 /**
  * 单个线程写
  */
 public void write(){
  rwLock.writeLock().lock();//写锁开启,这时只有一个写线程进入
  try {//用try finally来防止因异常而造成的死锁
   System.out.println(Thread.currentThread().getName()+"write lock is ready.."+sdf.format(new Date()));
  Thread.sleep(1000);//此处休眠1000ms,目的是为了让写锁看的清楚
  } catch (Exception e) {
   // TODO: handle exception
   e.printStackTrace();
  } finally {
   rwLock.writeLock().unlock();
  }  
 }
}

3.2、如何用Java实现阻塞队列,即如何实现生产者消费者模式?

Java实现阻塞队列是通过是通过BlockingQueue接口实现的, BlockingQueue最终会有四种状况,如下表:

 

我用的最多的是BlockingQueue接口的两个实现类:ArrayBlockQueue一个由数组支持的有界阻塞队列。第二个是LinkedBlockQueue:一个可改变大小的阻塞队列。

下面使用ArrayBlockQueue来实现之前实现过的生产者消/费者模式:

 

import java.util.concurrent.ArrayBlockingQueue;

 

public class ThreadProviderConsumer {

//装鸡蛋的盘子,大小为5

private static ArrayBlockingQueue<Object> eggs = new ArrayBlockingQueue<Object> (5);

 

/**

 * 放鸡蛋,生产者

 */

public static void putEggs(String egg) {

try {

eggs.put(egg);// 向盘子末尾放一个鸡蛋,如果盘子满了,当前线程阻塞  

System.out.println("生产者放鸡蛋");

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

 

/**

 * 取鸡蛋,消费者

 */

public static void getEggs() {

try {

eggs.take();//从盘子开始取一个鸡蛋,如果盘子空了,当前线程阻塞  

System.out.println("消费者取鸡蛋");

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

 

public static void main(String[] args) {

//启动十个生产者,放鸡蛋

for(int i=0;i<10;i++) {

new Thread(new Runnable() {

@Override

public void run() {

// TODO Auto-generated method stub

putEggs("生产者放鸡蛋");

}

}).start();

}

//启动十个消费者,取鸡蛋

for(int i=0;i<10;i++) {

new Thread(new Runnable() {

@Override

public void run() {

// TODO Auto-generated method stub

getEggs();

}

}).start();

}

}

}

 

 

3.3、用Java编程一个会导致死锁的程序?

/**

 * 用Java编程一个会导致死锁的程序

 * @author EX_KJKFB_GONGLI

 *

 */

public class DeadLock {

 private static Object locka = new Object();

    private static Object lockb = new Object();

 public static void main(String[] args) throws Exception{

  new Thread(new Runnable(){

   @Override

   public void run() {

    // TODO Auto-generated method stub

    synchronized (locka) {

     System.out.println("调用线程"+Thread.currentThread().getName()+"得到锁Locka");

     try {

      Thread.sleep(500);

     } catch (InterruptedException e) {

      // TODO Auto-generated catch block

      e.printStackTrace();

     }

     synchronized (lockb) {

      System.out.println("调用线程"+Thread.currentThread().getName()+"得到锁Lockb");

     }

    }

   }},"Thread-0").start();

  new Thread(new Runnable(){

   @Override

   public void run() {

    // TODO Auto-generated method stub

    synchronized (lockb) {

     System.out.println("调用线程"+Thread.currentThread().getName()+"得到锁Lockb");

     try {

      Thread.sleep(500);

     } catch (InterruptedException e) {

      // TODO Auto-generated catch block

      e.printStackTrace();

     }

     synchronized (locka) {

      System.out.println("调用线程"+Thread.currentThread().getName()+"得到锁Locka");

     }

    }

   }},"Thread-1").start();  

 }

3.4、Java 关键字volatile 与 synchronized 作用与区别

volatile:它所修饰的变量不保留拷贝,直接访问主内存中的,一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。

Synchronized:当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

区别:

 一、volatile是变量修饰符,而synchronized则作用于一段代码或方法。

 二、volatile只是在线程内存和“主”内存间同步某个变量的值;而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。

4、Java反射

4.1、什么是反射API?它是如何实现的?

答:反射是指在运行时能查看一个类的状态及特征,并能进行动态管理的功能。这些功能是通过一些类的反射API提供的,比如Class,Method,Field, Constructors等。使用的例子:使用Java反射API的getName方法可以获取到类名。

4.2、如何提高反射效率?

答:(1)、正确的使用反射API,比如反射时尽量不要getMethods()后再遍历筛选,而直接用getMethod(methodName)来根据方法名获取方法

(2)、灵活使用缓存:需要多次动态创建一个类的实例的时候,有缓存的写法会比没有缓存要快很多,使用时,只需从内存中调用即可

(3)将setAccessible(true),让我们在用反射时能够访问私有变量

5、Java序列化

5.1、什么是java序列化,如何实现java序列化?或者请解释Serializable接口的作用?

答:我们有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,例如,要将java对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个java对象变成某个格式的字节流再传输,但是,jre本身就提供了这种支持,我们可以调用OutputStream的writeObject方法来做,如果要让java 帮我们做,要被传输的对象必须实现serializable接口,这样,javac编译时就会进行特殊处理,编译的类才可以被writeObject方法操作,这就是所谓的序列化。需要被序列化的类必须实现Serializable接口,该接口是一个mini接口,其中没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的.

6、junit中before,beforeClass,after, afterClass的执行顺序?

答:@BeforeClass -> @Before -> @Test -> @After -> @AfterClass;

(1)、@BeforeClass:针对所有测试,只执行一次,且必须为static void。

(2)、@Before:初始化方法,对于每一个测试方法都要执行一次。

(3)、@AfterClass:针对所有测试,只执行一次,且必须为static void

(4)、@After:释放资源  对于每一个测试方法都要执行一次

二、算法

1、冒泡排序

/**

* 冒泡排序,从小到大排*/

public void bubbleSort(int[] arr){

for(int i=0;i<arr.length;i++){

for(int j=0;j<arr.length-i-1;j++){

if(arr[j]>arr[j+1]){

int temp = arr[j];

arr[j] = arr[j+1];

arr[j+1] = temp;

}

}

}

System.out.println(Arrays.toString(arr));

}

2、用java语言定义一个二叉树结构,并写出广度遍历方法?

 

/**

 * 创建二叉树,要注意以下几点

 * 1、将数组转化为节点,最后一个父节点arr.length/2-1,右边的子节点是否存在并不确定,需要单独处理

 * 2、左孩子节点的索引parentIndex*2+1,右孩子节点的索引parentIndex*2+2

 * @author gongli

 */

public class CreateBinary {

    private int[] arr = {1,2,3,4,5,6,7,8,9};

    private List<Node> nodeList = null;

    class Node{

        Node leftChild;

        Node rightChild;

        int data;

        Node(int newData){

            leftChild = null;

            rightChild = null;

            this.data = newData;

        }

    }

    //生成二叉树

    public List<Node> createBinary(){

        nodeList = new ArrayList<Node>();

        //将数组转化为节点

        for(int i=0;i<arr.length;i++){

            nodeList.add(new Node(arr[i]));

        }

        //对最后一个父节点前的节点按父子节点间的关系建立二叉树

        for(int parentIndex = 0;parentIndex<arr.length/2-1;parentIndex++){

            nodeList.get(parentIndex).leftChild = nodeList.get(parentIndex*2+1);

            nodeList.get(parentIndex).rightChild = nodeList.get(parentIndex*2+2);

        }

        int lastIndex = arr.length/2-1;//最后一个父节点

        //左孩子

        nodeList.get(lastIndex).leftChild = nodeList.get(lastIndex*2+1);

        if(arr.length%2==1){//数组的长度为基数则存在右孩子

            nodeList.get(lastIndex).rightChild = nodeList.get(lastIndex*2+2);

        }

        return nodeList;

    }

 

}

    

  

/**

 * 从上层向下层遍历,从左向右,广度遍历

 * @param root

 */

public static void printTreeFromTopTOBottom(TreeNode root)

{

    if (root == null) return;

 

    Queue<TreeNode> queue = new LinkedList<PrintTree.TreeNode>();

    queue.offer(root);

    while (!queue.isEmpty())

    {

        TreeNode node = queue.poll();

        System.out.print(node.val + " ");

        if (node.left != null)

        {

            queue.offer(node.left);

        }

        if (node.right != null)

        {

            queue.offer(node.right);

        }

    }

    System.out.println();

}

 

三、设计模式

1、单例模式

单例模式有以下特点:

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。

  单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、打印机、常被设计成单例。

 

public class Singleton {  

    private static class LazyHolder {  

       private static final Singleton INSTANCE = new Singleton();  

    }  

    private Singleton (){}  

    public static final Singleton getInstance() {  

       return LazyHolder.INSTANCE;  

    }  

}

 

或者

 

//饿汉式单例类.在类初始化时,已经自行实例化 ,天生是线程安全的

public class Singleton {

    private Singleton() {}

    private static final Singleton single = new Singleton();

    //静态工厂方法

    public static Singleton getInstance() {

        return single;

    }

}

2、工厂模式

工厂模式主要是为创建对象提供了接口。工厂模式一般可分为三类:

1. 简单工厂模式

2. 工厂方法模式

3. 抽象工厂模式

这三种模式从上到下逐步抽象,并且更具一般性。

使用工厂模式有以下两种情况:

1.在编码时不能预见需要创建哪种类的实例。

2.系统不应依赖于产品类实例如何被创建。

3.由于简单工厂很容易违反高内聚责任分配原则,因此一般只在很简单的情况下应用。

基于以上情况我们很容易理解spring在实例化对象的过程中就使用了,工厂模式,因为在实例化之前spring框架并不能预见需要创建哪种类的实例,比如spring中的AbstractFactoryBean类就通过实现FactoryBean接口,返回been实例,实现代码之间的低耦合。

2.1、简单工厂模式

组成:

1、工厂类角色:这是本模式的核心,含有一定的判断逻辑,,决定究竟应该创建哪个具体类的对象,工厂类角色在java中它往往由一个具体类实现。

2、抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。

3、具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。

 

简单工厂模式的一般化类图

2.2、工厂方法模式

组成:

1、抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。

2、具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。

3、抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。

4、具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

 

工厂方法模式的一般化类图

2.3、抽象工厂模式

使用场景:

1、系统中有多个产品族,而系统一次只可能消费其中一族产品
2、同属于同一个产品族的产品一起使用时。

3、抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的具体产品是什么,这样一来,客户就从具体的产品中被解耦。

组成:

1、抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。

2、具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。

3、抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。

4、具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

 

抽象工厂模式的一般化类图

3、建造者模式

使用场景:

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。我的理解就是:一个javaBean 如果想获取一个完整的对象, 需要为属性挨个赋值.这样如果连续创建多个对象的时候, 重复代码 及代码量明显太多了这时候就应该抽象出来一个 创建对象的接口,或者抽象类  每一类对象一个实现类.在创建一个控制类. 里面调用接口的每一个方法, 同时返回创建好的对象,。

在项目中的使用

小企业金融服务平台中,一位银行客户可能拥有多种贷款产品,比如张三拥有小微一般房产抵押业务,同时还拥有小额个人流水贷,各种贷款类型拥有的利率,还款期限,还款方式都不同,但我们可以把他记录到一个javabeen中,并进行实例化,有几种贷款产品,就要实例化几次很麻烦,此时我们就可以使用建造者模式,将一个复杂对象的构建与它的表示分离,

建造者(Builder)接口,主要定义贷款流程的业务逻辑,具体建造者(ConcreteBuilder)为业务逻辑的具体实现类,其中具体实现类就依赖于我们贷款产品的javabeen,通过业务申请的贷款具体类型,通过指挥者(Director),去调用指定业务类型的bulider接口就可以啦!

组成:

1、建造者(Builder):为创建一个产品对象的各个部件指定抽象接口。

2、具体建造者(ConcreteBuilder):实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。

3、指挥者(Director):指挥并构造一个使用Builder接口的对象。

4、产品(Product):表示被构造的复杂对象。

 

 

建造者模式的一般化类图

4、门面模式

使用场景:

外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

在项目中的使用

银行的BECIF系统是全行核心客户信息管理系统,银行的各个子系统客户相关信息都要与之进行交互,为了方便对各个系统客户的管理BECIF采用门面模式,为各个子系统提供一个门面类,这样BECIF系统无需关心各个子系统的具体实现方式,只需要调用门面,就能完成信息交互,同时也降低了BECIF系统,与各个子系统的耦合性。

组成:

1、门面角色:客户端调用这个角色的方法。此角色知晓相关的子系统的功能和责任。

2、子系统角色:可以同时有一个或者多个子系统。每个子系统都不是一个单独的类,而是一个类的集合,实际上门面仅仅是另一个客户端而已。

              

门面模式的一般化类图

四、线程池

1、线程池基础

1.1、Java内存模型是什么?

Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。Java线程之间的通信采用的是共享内存模型,线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。比如线程A与线程B之间如要通信的话,首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去,然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

1.2、Java中的volatile 变量是什么?

volatile是一个特殊的修饰符,只有成员变量才能使用它,该关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接写到主内存,即保证了变量的可见性,这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求。

1.3、Java中什么是竞态条件? 举个例子说明。

竞态条件会导致程序在并发情况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争,比如要从A,B两个本中读取数据,放到list中,要求A的内容在B之前,如果直接用两个线程分别调用AB执行顺序无法保证,出现bug,解决方案两种:一是使用线程的join方法,或者是使用countDownLatch类中的计数器进行处理。

 

1.4、如何在多个线程间共享数据?

举个说明吧:比如卖票,几个线程共同操作记录票数的那个变量,任务都是使它减一。针对这种情况,我们只需要写一个类实现Runnable接口即可,在run()方法中对这个票进行减一,然后将这个Runnalbe扔给多个线程去执行,自然它们就操作同一个data了。也就是说我们要实现某个数据在多个线程间的共享,我们就将该该数据写入一个run()方法中,然后用所有线程来执行该方法,就能实现数据共享了。

1.5、如何避免死锁?

(1)加锁顺序(线程按照一定的顺序加锁)

(2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

1.6、Java中活锁和死锁有什么区别?

活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。

1.7、怎么检测一个线程是否拥有锁?

在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。

1.8、写出3条你遵循的多线程最佳实践

1、给线程起个有意义的名字。

2、尽量缩小同步的范围。

3、多用同步类,比如CountDownLatch,少用wait 和 notify,因为这些同步类简化了编码操作,同时也使代码控制更加灵活。

4、多用并发集合少用同步集合,不如有使用HashMap的情况可以考虑去使用ConcurrentHashMap,ConcurrentHashMap采用锁分段技术,分段加锁,暂用资源较小,效率是非常高的。

1.9、什么是线程池?

线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程,以节省开销。

1.9.1、线程池如何调优?

1、合理设置最大线程数:当请求数量比较多,但每个请求时间,比较短,则可以适当增加最大线程数,比如说JVM有4个CPU可用,很明显最大线程数至少要设置为4,但也不能设置得太大,因为一旦服务器成为瓶颈,向服务器增加负载时非常有害的;其次对于CPU密集型或IO密集型的机器增加线程数实际会降低整体的吞吐量。

2、设置最小线程数:将最小线程数设置为其他某个值(比如1),出发点是为了防止系统创建太多线程,以节省系统资源。一般而言,对于线程数为最小值的线程池,一个新线程一旦创建出来,至少应该保留几分钟,以处理任何负载飙升。空闲时间应该以分钟计,而且至少在10分钟到30分钟之间,这样可以防止频繁创建线程。

3、缓存队列大小:等待线程池来执行的任务会被保存到某个队列或列表中;当池中有线程可以执行任务时,就从队列中拉出一个。这会导致不均衡:队列中任务的数量可能变得非常大。如果队列太大,其中的任务就必须等待很长时间,直到前面的任务执行完毕。对于任务队列,线程池通常会限制其大小。但是这个值应该如何调优,并没有一个通用的规则。若要确定哪个值能带来我们需要的性能,测量我们的真实应用是唯一的途径。不管是哪种情况,如果达到了队列限制,再添加任务就会失败。ThreadPoolExecutor有一个rejectedExecution()方法,用于处理这种情况,默认会抛出RejectedExecutionExecption。

2、多线程高并发模拟场景

 

2.1、平时只有千条左右的访问量,但是由于做广告,访问量变成了数万,并发量激增,怎么处理?

答:由于平时网站的并发量较小,网站并发突增,服务器宕机的风险是很高的,为了减小不必要的损失,网站有并发增高苗头时,比如做广告等,业务人员应事先向开发人员反馈,以便开发人员即使进行调整。

从技术角度上讲,在不增加硬件的情况下,可以先用jmeter模拟高并发,进行服务器负载测试,随着负载的增加,服务器响应将会变得缓慢,甚至宕机,此时我们需要分析是什么原因导致服务器缓慢,是应用的问题?带宽的问题?I/O问题?数据库的问题?或者其它的问题?

①如果是应用问题则,需要优化应用代码,这个应该是最优先考虑的,只有代码达到最优化才能最节省成本,合理配置线程池参数,配置缓存等;

②如果时带宽问题: 网站的并发太高,达到服务器的吞吐量极限,这个时候只有做负载均衡来分流。

③如果是I/O问题:比如频繁的图片或者文件读取,将应用服务器与图片或者文件服务器分离,可使用nginx作为文件服务器,而web容器只处理业务逻辑。

④如果是数据库问题:首先想到的当然是优化sql语句,在应用程序中使用redis等key-value缓存,如果数据插入或修改量比较大,还可以使用异步队列进行削峰操作,比如使用rabbitmq,如果以上仍不能解决问题,则可以考虑搭建数据库集群和和使用分库策略。

2.2、百万级并发设计?

1、HTML静态化

因为纯静态化的html页面,无需服务器渲染,可以直接被浏览器解析,效率是最高的,所以我们尽可能使我们的网站上的页面采用静态页面来实现,但是对于大量内容并且频繁更新的网站,我们无法全部手动去挨个实现,可以使用信息发布系统CMS实现。

2、图片服务器分离

比如可以使用nginx作为文件服务器,而web容器只处理业务逻辑。

3、数据库进行集群,分库。

4、使用缓存,比如redis。

5、采用负载均衡。

6、使用CDN静态代码加速技术。

7、使用异步队列进行削峰操作,比如使用rabbitmq。

五、Java虚拟机

1、运行时数据区包括哪些?

(1)程序计数器:是线程私有的,她是一块较小的内存空间,它可以看作是当前线程所执行的字节码行号指示器,如果计数器记录的是正在执行的java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;如果是Native方法,这个计数器记录的值则为null,注意程序计数器中没有OutOfMemmoryError异常。

(2)Java 虚拟机栈:是线程私有的,他的生命周期与线程相同,虚拟机棧描述的是Java方法执行的内存模型,如果线程请求的栈深度大于虚拟机所允许的深度,则抛出StackOverflowError异常,如果扩展时无法申请到足够的内存,则会抛出OutOfMemoryError异常。

(3)本地方法栈:是线程私有的,于虚拟机栈锁发挥的作用类似,只是本地方法栈则为虚拟机使用到的是Native中的方法,同样的本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常

 (4)Java堆:是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存的唯一目的就是存放对象实例,几乎所有对向实例都在这里分配内存,正因如此该区域是GC管理的主要区域

 (5)方法区:是各个线程的共享区,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据等。

 (6)直接内存:并不是虚拟机运行时数据区的一部分,他是随NIO一起引入的,直接内存,可以使用Native函数库直接分配对内存,然后通过存储在java堆中的DirectByteBuffer对象对这块内存进行操作,这样规避了在java堆和Native堆中来回复制数据,从而显著提高性能,但值得注意的是如果扩展时无法申请到足够的内存,则会抛出OutOfMemoryError异常,这点很容易被忽略。

2、Java 的4种引用方式?

1、强引用:就是直接new出对象,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。

2、软引用:用来描述一些还有用,但并非必须的对象。软引用所关联的对象,有在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围。

3、弱引用:描述非必须的对象,只能生存到下一次垃圾收集发生前。

4、虚引用:一个对象是否有虚引用,完全不会对其生存时间够成影响,为一个对象关联虚引用的唯一目的,就是希望在这个对象被收集器回收时,收到一个系统通知。

3、虚拟机的垃圾收集算法? 

3.1、什么是标记-清除算法?

算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

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

 

3.2、什么是复制算法?

答:它将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这块的内存用完了,就将还活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。

 不足:将内存缩小为了原来的一半。

 实际中我们并不需要按照1:1比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor,当另一个Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配到老年代。

3.3、标记整理算法?

让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

3.4、分代收集算法?

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

4、虚拟机判断对象存活的算法?

4.1、引用计数器法?

答: 给对象添加一个引用计数器,每当由一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。引用计数算法不能解决对象之间的相互引用问题,在虚拟机中一般不会采用。

4.2、可达性分析算法?

答:以“GC Roots”对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。Java语言中GC Roots的对象包括下面几种:

1.Java虚拟机栈中引用的对象。 2.方法区中类静态属性引用的对象。

3.方法区中常量引用的对象。 4.本地方法栈引用的对象。

  

5、虚拟机中对象如何自救?

答:使用Finalize()方法,任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行,因此第二段代码的自救行动将会无效。此外使Finalize()方法时,虚拟机并不保证能规避对对象的回收,也就是说使用Finalize()方法的对象,仍然可能被回收。

6、垃圾收集器 

6.1、Serial收集器

答:这个收集器是一个单线程的收集器,但它的单线程的意义不仅仅说明它会只使用一个CPU或一条收集线程去完成收集工作,更重要的是它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。  

6.2、CMS收集器

CMS收集器是基于标记清除算法实现的,整个过程分为4个步骤:

1.初始标记2.并发标记3.重新标记4.并发清除

优点:并发收集、低停顿

缺点:

1.CMS收集器对CPU资源非常敏感,CMS默认启动的回收线程数是(CPU数量+3)/4,

2.CMS收集器无法处理浮动垃圾,也就是无法清理并发清理阶段还在运行的内存,可能出现Failure失败而导致一次Full GC场地产生

3.CMS是基于标记清除算法实现的

6.3、G1收集器

1.并行与并发:利用多CPU缩短GC停顿的时间

2.分代收集算法

3.空间整合:不会产生内存碎片

运作方式:初始标记,并发标记,最终标记,筛选回收

7、内存分配有哪些原则?

一般对象优先分配在 Eden,大对象直接进入老年代,长期存活的对象将进入老年代

7.1、新生代的特性

答:采用复制算法,主要用来存储新创建的对象,内存较小,垃圾回收频繁。这个区又分为三个区域:一个 Eden Space 和两个 Survivor Space。

(1)、当对象在堆创建时,将进入年轻代的Eden Space。

(2)、垃圾回收器进行垃圾回收时,扫描Eden Space和A Suvivor Space,如果对象仍然存活,则复制到B Suvivor Space,如果B Suvivor Space已经满,则复制 Old Gen

(3)、扫描A Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个Old对象,则将其移到Old Gen。

(4)、扫描完毕后,JVM将Eden Space和A Suvivor Space清空,然后交换A和B的角色(即下次垃圾回收时会扫描Eden Space和B Suvivor Space。

7.2、老年代的特性

答:主要用来存储长时间被引用的对象。它里面存放的是经过几次在新生代进行扫描判断过仍存活的对象,内存较大,垃圾回收频率较小。

8、类加载器

8.1、类加载器的作用是什么?

答:类加载器实现类的加载动作,同时用于确定一个类。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。即使两个类来源于同一个Class文件,只要加载它们的类加载器不同,这两个类就不相等。

8.2、类加载器有哪些?

(1)、启动类加载器:由BootStrap是虚拟机自身的一部分,负责将存放在\lib目录中的类库加载到虚拟机中,其无法被Java程序直接引用。

(2)、扩展类加载器:由ExtClassLoader实现,负责加载\lib\ext目录中的所有类库,开发者可以直接使用。

(3)、应用程序类加载器:由APPClassLoader实现。负责加载用户类路径(ClassPath)上所指定的类库。

注意:加载顺序为(1)-->(2)-->(3)

8.3、类加载机制

8.3.1、什么是双亲模型?

答:双亲委派模型,要求除了顶层的启动类加载器外,其余加载器都应当有自己的父类加载器。

工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有到父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

8.3.2、为什么要使用双亲委派模型?

答:双亲委派机制能使Java类随着它的类加载器一起具备了一种带优先级的层次关系,

如果没有使用双亲委派模型,让各个类加载器自己去加载,那么Java类型体系中最基础的行为也得不到保障,应用程序会变得一片混乱。

8.3.3、什么是类加载机制?

答:Class文件描述的各种信息,都需要加载到虚拟机后才能运行。虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

9、Java虚拟机调优

9.1虚拟机常用参数

-Xms:设置java应用程序启动时的初始堆大小

-Xmx:设置java应用程序能获得的最大堆大小

-Xss:设置线程栈的大小

-XX:NewSize : 设置新生代的大小。

-XX:NewRatio:设置老年代与新生代的比例,即老年代除以新生代大小。

-XX:SurviorRatio:新生代中eden区与survivior 区的比例。

-XX:PermSize:设置永久区的大小。

-XX:MaxPermSize:设置最大永久区的大小。

-XX:TargetSurvivorRatio:设置survivior 的使用率,当达到这个空间使用率时,将对象送入老年代。

-XX:ParallelGCThreads:配置并行收集器的线程数,此值一般配置为与CPU数目相等。

-XX:+UseConcMarkSweepGC 设置年老代为并发收集,收集的目标是尽量减少应用的暂停时间,减少Full GC发生的几率。

9.2、虚拟机参数在调优中的使用

(1)、-Xms、 -Xmx 通常设置为相同的值,避免运行时要不断扩展JVM内存。

(2)、-XX:PermSize、-XX:MaxPermSize 用来控制方法区的大小,通常设置为相同的值。

(3)、-XX:NewSize新生代的大小,如果minor GC次数频繁或是大量对象直接进入老年代,,就应适当增加新生代大小。

9.3、虚拟机实例调优

六、Web应用服务器

1、Tomcat

1.1、Tomcat的缺省是多少,怎么修改?

答:Tomcat的缺省端口号是8080,修改Tomcat端口号:

修改conf目录下的server.xml文件,

1.2、Tomcat调优

1.3、tomcat怎么设置访问线程数目?

(1)、maxThreads:Tomcat线程池最多能启动的线程数;

(2)、maxConnections:Tomcat最多能并发处理的连接;

(3)、acceptCount:Tomcat维护最大的队列数;

(4)、minSpareThreads:Tomcat最小备用线程数,tomcat启动时的初始化的线程数。

1.4、怎样调整tomcat的内存?

答:Tomcat本身不能直接在计算机上运行,需要依赖于操作系统和一个java虚拟机。所以想调整Tomcat的启动时初始内存和最大内存就需要向JVM声明,一般的JAVA程序在运行都可以通过中-Xms -Xmx来调整应用程序的初始内存和最大内存。这两个参数在server.xml中的JAVA_OPTS里面配置。

1.5、Tomcat有几种部署方式?

第一种方法:在tomcat中的conf目录中,在server.xml中的,<host>节点中添加:

<Context>节点,配置path项目名称属性和docBase工程路径,具体到war包。

这个方法有个优点,就是服务器端运行的项目名称为path,外部访问的URL则使用server.xml的命名。

第二种方法:将web项目文件件拷贝到webapps 目录中。

第三种方法:直接上传war就可以。

2、WebSphere

2.1、在WebSphere里面如何部署一个应用?

答:(1)、热部署:直接把文件拷贝到websphere得deployedapplication文件夹里。

(2)、WAS的管理页面部署:

浏览器中输入 http://<server>:<port>/admin,注意默认端口为9060

在Applications→Application Types→WebSphere enterprise applications 选项卡中,单击install 即可开始安装 war包。

(3)、启动和停止服务

第一种方法WAS管理页面启动或停止。

第二种方法:WebSphere的bin目录下,启动命令./startServer.sh sh服务脚本,

./stopServer.sh 服务脚本。

 

七、nginx

1、请解释一下什么是Nginx?

答:Nginx是一个web服务器和反向代理服务器,用于HTTP、HTTPS、SMTP等协议。

2、请列举Nginx的一些特性?

答:本人使用Nginx主要用于两方面:

(1)、作为图片和文件服务器。

(2)、实现反向代理和负载均衡器。

3、什么是epoll模型?

答:epoll模型常常用于处理服务端的并发问题,是Linux特有的I/O函数,其特点如下:

    1.epoll是Linux下多路复用IO接口。

2.epoll之所以高效,是因为epoll将用户关心的文件描述符放到内核

中,epoll无须遍历整个事件,只要遍历那些被内核IO事件唤醒的事件就可以了,这就   好比一群女孩子又漂亮的,有丑的,我只对漂亮女孩子感兴趣,使用epoll模型后,无需去看每一个女孩子,模型会替你选出漂亮女孩并反馈,效率当然提高啦!

4、为什么要用Nginx?

答:(1)、跨平台、配置简单。

    (2)、非阻塞、高并发连接:能够轻松处理2-3万并发连接数。

    (3)、内存消耗小:开启10个nginx才占150M内存。

    (4)、稳定性高:宕机的概率非常低。

    (5)、接收用户请求是异步的:浏览器将请求发送到nginx服务器,它先将用户请求全部接收下来,再一次性发送给后端web服务器,极大减轻了web服务器的压力。

    (6)、能够一边接收web服务器的返回数据,一边发送给浏览器客户端。

(7)、网络依赖性比较低,只要ping通就可以负载均衡。

 

 

 

5、Nginx如何配置作为图片服务器?

答:在nginx的conf/nginx.conf文件中,在server里再添加一个location并指定实际路径,

 

location /images/ {

    root  /home/ftpuser/www/;-->图片放置路径

    autoindex on;

}  

6、Nginx如何实现负载均衡?

答:负载均衡是用户访问网站时,先访问一个中间服务器,在让这个中间服务器在服务器集群中选择一个压力较小的服务器,其原理很简单:一个nginx的worker处理监听句柄(一个标识符),当达到最大连接数的7/8时,本worker不会再试图拿锁建立连接,这样其他worker就有就有机会建立新连接了。

Nginx实现负载均衡的方式主要是轮询,weight ,ip_hash ,fair四种方式实现(按后端服务器的响应时间来分配请求,响应时间短的优先分配),只需配置upstream即可

upstream 域名{

    server 192.168.0.14;    

 server 192.168.0.15;

}

7、Nginx负载均衡如何实现session共享?

答:我熟悉的主要通过三种方式实现:

(1)、将需要共享的信息存入cookie,cookie是存放在客户端的,安全性相对较低,如果客户端cookie信息较大,会加重服务器 的负担,更重要的一点是如果客户端关闭cookie,导致访问失败,会给网站或者应用系统带来损失。

(2)、将session存入redis缓存,当然也可以存入mysql、oracle等关系型数据库,但频繁读取,明显会加重关系型数据库的负担,得不偿失。

(3)、在upstream中配置ip_hash指令,ip_hash技术能够将某个ip的请求定向到同一台后端应用服务器,但值得注意的是nginx获取的ip一定要是真实的客户端ip,否则ip_hash就失效了。ip_hash实现如下:

upstream 域名{

    server 127.0.0.1:8080;    

    server 127.0.0.1:8081;

ip_hash;

}

 

 

 

8、如何解决惊群现象?

答:所谓惊群现象是多个子进程在同一时刻监听同一个端口引起的,当这个事件发生时,这些线程/进程被同时唤醒,可以想见,效率很低下,

Nginx解决方法:同一个时刻只能有唯一一个子进程监听同一个端口。

9、Nginx为什么不采用多线程?

答:Nginx: 采用单线程来异步非阻塞处理请求,不会为每个请求分配cpu和内存资源,节省了大量资源,同时也减少了大量的CPU的上下文切换。所以才使得Nginx支持更高的并发。

 

 

八、redis  

1、什么是redis?

答:Redis 是一个基于内存的高性能key-value数据库。

2、redis有哪些特点?

答:Redis本质上是一个Key-Value类型的内存数据库,整个数据库统统加载在内存当中进行操作,因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,当然redis也有缺点,因为数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

3、redis分布式

答:redis支持主从的模式。原则:主会将数据同步到从,而从不会将数据同步到主。从启动时会连接主来同步数据。这是一个典型的分布式读写分离模型。我们可以利用主来插入数据,从提供检索服务。这样可以有效减少单个机器的并发访问数量。当然这种读写分离模型也会带来弊端,不管是Master(主)还是Slave(从),每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力的限制。为了解决读写分离模型的缺陷,redis引入了数据分片模型,可以将每个节点看成都是独立的master,然后根据业务需要将每个master设计成由一个master和多个slave组成的模型。

 

 

4、 使用Redis有哪些好处?

(1) 速度快,因为数据存在内存中。

(2) 支持丰富数据类型,支持string,list,set,hash

(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行。

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

 

5、什么是redis的RDB备份和AOF备份?

 

答:首先RDB和AOF都可以进行数据备份,当两者可以共存时,二者是是协作,不会冲突,只不过首先启动找的是AOF,我们使用RDB进行保存时候,如果Redis服务器发送故障,那么会丢失最后一次备份的数据!而AOF方式可以规避这一问题,因为以日志的形式来记录每个写操作,只许追加不许改写,所以要恢复数据,只需要把日志内容从头到尾执行一次就行了。

 

6、 redis常见性能问题和解决方案?

(1) Master最好不要做任何持久化工作。

 

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

 

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

 

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

 

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

 

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

 

 

 

 

 

 

 

 

7、 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?

答:我们可以使用对缓存数据进行定时,当redis数据大于20w条时对于多出的数据,更具业务需求设置合理的时间,超过这个时间限制的数据删掉,这样保持的数据永远都是热点。

8、怎么将session缓存到redis中?

答:我是直接使用jredis类库中的方法,直接将session写入redis。

9、redis中的常用命令有哪些?

(1) 、keys 命令: ?匹配一个字符;* 匹配任意个(包括0个)字符;[]匹配括号间的任一个字符;

(2) 、exists key:断一个键值是否存在,如果存在,返回整数类型 1 ,否则返回 0;

(3) 、del key [key.....]:可以删除一个或多个键,返回值是删除的键的个数;

(4) 、type key:获得键值的数据类型;

(5) 、String赋值与取值,set key value赋值与取值,get key取值;

(6) 、incr key:递增数字当要操作的键不存在时会默认键值为 0 ;

(7) 、decr key:减少数字;

(8) 、append key value:作用是向键值的末尾追加 value;

(9) 、strlen key:返回键值的长度,如果键不存在则返回0;

(10) 、hset key field value:给hash赋值,hget key field:获取hash值;

(11) rupsh list value:向list列表尾部增加元素value,rpop list删除列表尾部元素。

10、redis主从是怎么选取的 ? 

答:使用slaveof NO ONE命令,可以将slave切换成master。

11、redis主节点宕机了怎么办,还有没有同步的数据怎么办?

答:redis主节点宕机,是相当危险的,切记不能尝试马上重启,这样会清空主从节点的所有数据,后果非常严重,正确做法是连上从库,做save操作,将会在从库的data目录保存一份从库最新的dump.rdb文件。将这份dump.rdb文件拷贝到主库的data目录下,再重启主库。当然这样恢复的前提是从库配置采用AOF备份模式。

九、http、https、tcp、udp协议

1、http协议

1.1、什么是Http协议无状态协议?怎么解决Http协议无状态协议?

答:1、无状态协议对于事务处理没有记忆能力。

    2、无状态协议解决办法: 通过1、Cookie 2、通过Session会话保存。

 

1.2、说一下Http协议中302状态?

答:http协议中,返回状态码302表示重定向。

这种情况下,服务器返回的头部信息中会包含一个 Location 字段,内容是重定向到的url。

1.3、Http请求协议由什么组成?

答:a、请求行:包含请求方法、URI、HTTP版本信息;

b、请求首部字段;

c、请求内容实体。

1.4、Http响应报文包由什么组成?

答:a、状态行:包含HTTP版本、状态码、状态码的原因短语;

    b、响应首部字段;

c、响应内容实体。

 

1.5、常用的HTTP方法有哪些?

 答:GET: 用于请求访问已经被URI(统一资源标识符)识别的资源,可以通过URL传参给服务器

 POST:用于传输信息给服务器,主要功能与GET方法类似,但一般推荐使用POST方式。

 PUT: 传输文件,报文主体中包含文件内容,保存到对应URI位置。

 HEAD: 获得报文首部,与GET方法类似,只是不返回报文主体,一般用于验证URI是否有效。

 DELETE:删除文件,与PUT方法相反,删除对应URI位置的文件。

 

1.6、Http协议各部分字段参数?

答:(1)、通用首部字段(请求报文与响应报文都会使用的首部字段)

 Date:创建报文时间、Connection:连接管理、Cache-Control:缓存的控制、Transfer-Encoding:报文主体的传输编码方式;

 (2)、请求报文会使用的首部字段

 Host:请求资源所在服务器、Accept:可处理的媒体类型、Accept-Charset:可接收的字符集、 Accept-Encoding:可接受的内容编码、Accept-Language:可接受的自然语言;

 (3)、响应报文会使用的首部字段

 Accept-Ranges:可接受的字节范围、Location:令客户端重新定向到的URI、Server:HTTP服务器的安装信息;

 (4)、请求报文与响应报文的的实体部分使用的首部字段

 Allow:资源可支持的HTTP方法、Content-Type:实体主类的类型、Content-Encoding:实体主体适用的编码方式、Content-Language:实体主体的自然语言、Content-Length:实体主体的的字节数。

1.7、Http协议有那些特征?

答:1、支持客户/服务器模式;2、简单快速;3、无连接;4、无状态;

1.8、请描述下https工作原理?

答:首先HTTP请求服务端生成证书,客户端对证书的有效期、合法性、域名是否与请求的域名一致、证书的公钥等进行校验;客户端如果校验通过后,就根据证书的有效公钥, 生成随机数,随机数使用公钥进行加密与服务端进行交互。

2、TCP与UDP

2.1、TCP与UDP有哪些区别?

答:(1)、TCP面向连接,UDP是无连接的;

 

(2)、TCP提供可靠的服务,UDP尽最大努力交付,但不保证可靠交付;

 

(3)、UDP具有较好的实时性,工作效率比TCP高,因为每一条TCP连接只能是点到点;UDP支持一对一,一对多,多对一和多对多的交互通信;

(4)、TCP对系统资源要求较多,UDP对系统资源要求较少。

 

 

2.2、什么是TCP粘包现象?

答:TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

2.3、为什么出现粘包现象?

答:(1)、发送方原因:TCP默认会使用Nagle算法。而该算法会收集多个小组,在一个确认到来时一起发送,这样就可能产生粘包。

    (2)、接收方原因:如果TCP接收分组的速度大于应用程序读分组的速度,多个包就会被存至缓存,应用程序读时,就会读到多个首尾相接粘到一起的包。

2.4、如何处理粘包现象?

答:发送方处理:关闭Nagle算法;或者接收方处理:发送每条数据的时候,将数据的长度一并发送,循环读取数据,直到所有的数据都被处理。

十、Jsp和Servlet

1、Jsp

1.1、解释Jsp的生命周期?

答:(1)、编译阶段:servlet容器编译servlet源文件,生成servlet类;

 

  (2)、初始化阶段:加载与JSP对应的servlet类,创建其实例,并调用它的初始化方法;

 

  (3)、执行阶段:调用与JSP对应的servlet实例的服务方法;

 

  (4)、销毁阶段:调用与JSP对应的servlet实例的销毁方法,然后销毁servlet实例。

 

1.2、Jsp有哪九大Jsp内置对象?

答:Request,Response,Out,Session,Application,Cookie,Config,Page,Exception。

 

 

 

 

2、Servlert

2.1、说一说Servlet生命周期?

答:Servlet生命周期包括三部分:

 

初始化:Web容器加载servlet,调用init()方法;

 

处理请求:当请求到达时,运行其service()方法;

 

销毁:服务结束,web容器会调用servlet的distroy()方法,销毁servlet。

十一、Spring

1、IOC容器

1.1、什么是控制反转(IOC)?什么是依赖注入?

答:IOC不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在对象内部直接控制。而所谓的反转是由于容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象。依赖注入即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

1.2、谈谈你个人对IOC的理解?

答:在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成,在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,而使用了Spring之后就不一样了,创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题(你是什么时候生的,怎么生出来的我可不关心,能帮我干活就行),A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。即对象之间的依赖转换成了对IOC容器的依赖,从而完成对象之间的解耦。

1.3、请解释下Spring框架中的IOC?

答:Spring中的 org.springframework.beans 包和 org.springframework.context包构成了Spring框架IoC容器的基础。org.springframework.beans.factory.BeanFactory 是Spring IoC容器的具体实现,用来包装和管理各种bean。BeanFactory接口是Spring IoC 容器的核心接口。我们通过IOC容器把对象的创建、初始化、销毁交给spring来管理,而不是由开发者控制,实现控制反转,达到了对象之间解耦的目的。

2、aop

2.1、什么是aop,为什么要使用aop?

答:aop即为面向切面编程,aop实现原理其实是java动态代理,但是jdk的动态代理必须实现接口,所以我们又引入了cglib这个类库,所以可以做到不实现接口的情况下完成动态代理,使用aop的目的很简单,就是让我们编程时只关注主流业务逻辑的编写,对那些需要但又不是主流业务的内容用aop实现,比如日志,事务等等。

--------------------- 本文来自 加速奔跑的蜗牛 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/gongxun344/article/details/79595914?utm_source=copy

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值