java面试基础问题 整合

本文深入探讨Java中的关键概念和技术,包括基本数据类型与包装类的区别、线程安全、集合类的使用、设计模式、排序算法、SpringAOP原理等,旨在帮助读者掌握Java开发的核心技能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

目录 1

1javaIntegerint的区别? 3

2javaStringBufferString的区别? 3

3java种的线程安全和非线程安全的区别: 4

4、如何保证线程安全: 4

5、如何实现线程安全: 4

6、如何实现线程同步: 5

7、线程安全和非安全的集合: 5

8、单例模式: 5

9、工厂模式: 7

10、冒泡排序: 9

11、快速排序: 10

12Spring AOP实现原理: 11

13、链式队列的实现原理: 12

14java虚拟机的内存分区和作用: 14

15BST的时间复杂度: 16

16BSTjava实现 16

17、数据结构 求图的两点的最短路径 17

18java数据库连接池的实现和工作原理: 19

19、二分查找的实现: 23

20java实现全排列: 24

21Javavolatile的作用 25

22、动态代理: 26

23JAVA垃圾回收机制(GC 31

24、基本类型(原始类型)和包装类(引用类型)的区别: 34

25HashMapHashSet的实现原理: 34

26HashMapHashTable的区别: 36

27、数据库的ACID 36

28java线程池: 37

29、解决哈希冲突的四种方法: 41

30Object类中有哪些方法: 41

31、内存溢出的解决方法 42

32String转出 int型,如何判断能不能转?怎么转? 43

33、多线程的原理: 43

34、数据仓库的原理: 44

35map有哪些种类: 45

36hashmap迭代的三种方式: 46

37equals和“=”的区别 47

38、寻找第K小的数,解释复杂度。 47

39、索引的概念及特点; 48

40、使用linux命令求得一个文件中ip/字符串出现的次数: 49

41重入锁、对象锁、类锁的关系 51

42、自旋锁 52

43、栈内存溢出的原因: 53

44、双向链表 前插、尾插、指定位插的实现。 53

45、抽象类和接口的区别: 56

46TCPUDP的区别: 57


1javaIntegerint的区别?

1int是基本类型,Integerint封装的包装类,也就是类

2)声明为int的变量不需要实例化,声明为Interger的变量需要实例化

3)int 的缺省默认值为0,Integer的缺省默认值为null。

4)int 主要用在数值计算、参数传值上,Integer主要用再对象引用上,比如map 、list、set的值

2javaStringBufferString的区别?

1StringBuffer是字符串变量,String是字符串常量

2StringBuffer对象的内容可以修改;而String对象一旦产生后就不可以被修改

3)每次对String的操作都会生成新的String对象重新赋值其实是两个对象String的操作都是改变赋值地址而不是改变值操作

4)StringBuffer对象都有一定的缓冲区容量字符串大小超过容量时,自动增加容量16bye

5)StringBuffer类的方法是多线程、安全的,而StringBuilder不是线程安全的

6)运行速度:StringBuilder >  StringBuffer  >  String

7)String优点编译器可以把字符串设为共享的经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合 

8)StringBuffer的常用方法:

   sb.append(true);       //末尾添加true变量

   sb.deleteCharAt(1);    //删除1位

   sb. delete (1,4)       //1到3;

   sb.insert(4,“false”);//4位插入false

   sb.reverse();          //反转

   sb.setCharAt(1,’D’); //1位改变为D

   sb.length();           //长度

   sb.replace(0,1,"qqq"); //0到1替换为qqq

   sb.toString()          //转换为String:

3java种的线程安全和非线程安全的区别:

1)线程安全的判断:进程中多个线程同时运行这段代码如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

2)线程安全分为五部分:

A.不可变对象:String Integer

B.线程安全:  管运行时环境如何排列,线程都不需要任何额外的同步

C.有条件线程安全: HashtableVector单独操作线程安全,某些操作需要外部同步

D.线程兼容 :ArrayListHashMap不是线程安全的,可以通过同步在并发中安全使用

E.线程对立:不管是否调用了外部同步都不能在并发安全地呈现的类调用 System.setOut()

3)线程安全原理:采用加锁机制,当一个线程访问该类的某个数据时,进行保护,其他进程不能访问它直到该进程读取完。不会出现数据不一致或者数据污染。

4、如何保证线程安全:

1)不在线程之间共享状态变量

2)将状态变量修改为不可变的变量

3)在访问状态变量时实现同步

5、如何实现线程安全:

1)使用 synchronized 关键字来获取锁实现同步。(java对象都有个内置锁

2)同步方法分解。(使用一个动态的“锁定-释放-锁定-释放”方法synchronized 关键字使用在代码块而不是方法,当方法不用时就先释放给别方法用,需要用时再拿回来。

3)内部类。(一个方法定期需要调用一个类时,可以构造一个嵌套类)

4)事件驱动处理。(使用 wait()、notify() 和 notifyAll() 方法

5)访问缓慢资源 ― 文件、目录、网络套接字和数据库 ― 的方法放在一个单独的线程中,最好在任何 synchronized 代码之外

6、如何实现线程同步:

1)synchronized关键字修饰的方法同步方法

2)synchronized关键字修饰的语句块同步代码块

3)调用内部类,创建线程。

4)使用特殊域变量(volatile)实现线程同步对同步变量添加volatile关键字。

5)JavaSE5.0中新增了java.util.concurrent包来支持同步重入锁(ReentrantLock类

6)使用 wait()、notify() 和 notifyAll() 方法

7、线程安全和非安全的集合:

线程安全

线程非安全

Vector

Arraylist

StringBuffer

StringBuilder

HashTable(不允许空值)

HashMap(允许空值)、HashSet

Statck(继承Vector)

TreeSet(排序),TreeMap

 

8、单例模式:

单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。

饿汉式单例在方法调用前,实例就已经创建好了线程安全

1. //饿汉式单例类.在类初始化时,已经自行实例化   

2. public class Singleton1 {  

3.     private Singleton1() {}  

4.     private static final Singleton1 single = new Singleton1();  

5.     //静态工厂方法   

6.     public static Singleton1 getInstance() {  

7.         return single;  

8.     }  

9. }  

 

懒汉式单例在方法调用获取实例时才创建实例线程不安全

如何使它变线程安全的:

1)方法上加synchronized关键字

·  //懒汉式单例类.在第一次调用的时候实例化自己   

·  public class Singleton {  

·      private Singleton() {}  

·      private static synchronized Singleton single=null;  

·      //静态工厂方法   

·      public static Singleton getInstance() {  

·           if (single == null) {    

·               single = new Singleton();  

·           }    

·          return single;  

·      }  

·  }

2)双重检查锁定

·  //懒汉式单例类.在第一次调用的时候实例化自己   

·  public class Singleton {  

·      private Singleton() {}  

·      private static Singleton single=null;  

·      //静态工厂方法   

·      public static Singleton getInstance() {  

·           if (single == null) {    

          synchronized Singleton .Class{

           if (single == null) {    

·                  single = new Singleton();  

          }

·             }    

        }

       return single; 

·       }      

·  }

3)静态内部类(既解决了安全,又避免了性能问题)

1. public class Singleton {    

2.     private static class LazyHolder {                         //静态嵌套类

3.        private static final Singleton INSTANCE = new Singleton();    

4.     }    

5.     private Singleton (){}    

6.     public static final Singleton getInstance() {    

7.        return LazyHolder.INSTANCE;    

8.     }    

9. }    

饿汉单例模式:在创建的同时就实例化一个静态对象。永远占内存。线程安全。

懒汉单例模式:在第一次使用该单例时才会实例化对象。可通过以上三种形式实现安全。

如果不需要外部通过构造函数传入参数的话,就用饿汉式,否则的话就用懒汉式。用懒汉式的时候要记得考虑线程安全的问题

9、工厂模式:

创建一个对象时会遇到:你可能需要计算或取得对象的初始设置; 选择生成哪个子对象实例; 或在生成你需要的对象之前必须先生成一些辅助功能的对象

建立一个工厂模式就能解决上面的问题。

工厂模式可以分为三类: 

1)简单工厂模式(Simple Factory 
2)工厂方法模式(Factory Method 
3)抽象工厂模式(Abstract Factory 

工厂方法模式抽象工厂模式的区别:

工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。   
一个抽象工厂类,可以派生出多个具体工厂类。   
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。   
一个抽象工厂类,可以派生出多个具体工厂类。   
每个具体工厂类可以创建多个具体产品类的实例。

 

简单工厂模式

 

工厂方法模式

 

抽象工厂模式

10、冒泡排序:

Class test{

  Public void mpsort (int []a,int n){

    Int temp;

   For(int i=0;i<n;i++){

    For(int j=n-1;j>i;j--){

      If(a[j]<a[j-1]){

        Temp=a[j-1];

        a[j-1]=a[j];

        a[j]=temp;

      }

    }

  }

}

 

优化后的冒泡排序(添加一个判断,判断后面的是否已经有序)

Class test{

  Public void mpsort (int []a,int n){

int temp;

int f=1;

    for(int i=0;i<n&&f;i++){

    f=0;

     for(int j=n-1;j>i;j--){

      if(a[j]<a[j-1]){

        temp=a[j-1];

        a[j-1]=a[j];

        a[j]=temp;

        F=1;

      }

    }

  }

}

 


11、快速排序:

public class test{

   public static void sort(int array[],low,high){

     int i;

 int j;

 int index;

 if(low>high){

   returnn ;

 }

 i=low;

 j=high;

 index=array[i];

 while(i<j){

   while(i<j&&array[j]>=index)

     j--;

   if(i<j)

     array[i++]=array[j];

    

   while(i<j&&array[i]<index)

     i++;

   if(i<j)

 array[j--]=array[i];    

 }

 array[i]=index;

 sort(array,low,i-1);

 sort(array,i+1,high);

   }

 

   public static void quicksort(int array[]){

    sort(array,0,array.lengh-1)

   }

 

}

 


12Spring AOP实现原理:

AOP是面向方面编程

OOP是面向对象编程

AOP相当于OOP的补充和完善

Aop 的作用在于分离系统中的各种关注点,将核心关注点横切关注点分离开来。

核心关注点:业务处理的主要流程

横切关注点:与业务逻辑流程关系不大,经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理

AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心 圆柱体剖开,以获得其内部的消息。

实现AOP的技术,主要分为两大类

一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;

二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

AOP使用场景

AOP用来封装横切关注点,具体可以在下面的场景中使用:

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging  调试

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence  持久化

Resource pooling 资源池

Synchronization 同步    Transactions事务

13、链式队列的实现原理:

public class CustomLinkQueue<E> {

    //定义一个内部类Node,Node实例代表链栈的节点。

    private class Node {

        private E data;

        private Node next;

        public Node() {         //无参数的构造器

        }

 

        private Node(E data) { //初始化节点的数据域

            this.data = data;

        }

 

        public Node(E data, Node next) { //初始化全部属性的构造器

            this.data = data;

            this.next = next;

        }

    }

 

    private Node front;  //头指针指向头结点

    private Node rear;   //尾节点

private int count;   //该队列元素的数量

 

    /* *初始化队列*  此时队列为空*/

    public CustomLinkQueue() {

        Node p = new Node();          //新建一个辅助结点,后面出队也会用到

        p.data = null;

        p.next = null;

        front = rear = p;

}

 

    /* *入队* 在队列的后端插入节点 */

public void enqueue(E item) {

         Node newNode = new Node();

         newNode.data = item;

         newNode.next = null;        //入队的节点没有后继节点

         this.rear.next = newNode; //原来的尾节点的后继节点指向新节点

         this.rear = newNode;      //rear指向最后一个节点

         count++;

         

    }

 

 

 

    /*  *出队* 在队列的前端删除节点 */

    public E dequeue() throws Exception {

        if (isEmpty()) {                     //删除前判断队列是否为空

            throw new Exception("队列为空");

        } else {

            E obj;                            //obj用来存放要删除结点的数据

            Node p = this.front.next;     //队头指向的第一个节点

            obj = p.data;

            this.front.next = p.next;

 

            if (rear == p) {                //是否只剩下一个结点

                rear = front;

            }

            count--;

            return obj;

        }

    }

 

    /*  *队列的个数* */

    public int size() {

        return count;

    }

 

    /*  *遍历* 移动front指针,直到front指针追上rear指针 */

    public void traverse() {

     for (Node current = front.next; current != null; current = current.next) {

          System.out.println(current.data);

    }

   /*

  Node current=front;

  while(current!=rear){

    System.out.println(current.next.data+”\t”);

     current = current.next;

  }

*/

    }

 

    /*  *判断队列为空*  条件front == rear */

    public boolean isEmpty() {

        return front == rear;

         //return count==0;

    }

 

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

        CustomLinkQueue linkQueue = new CustomLinkQueue();

 

        for (int i = 0; i < 5; i++) {          //添加5个元素

            linkQueue.enqueue("lyx" + i);

        }

 

        System.out.println(linkQueue.size()); //打印个数

        System.out.println("===========traverse===========");

        linkQueue.traverse();                     //打印

        System.out.println("==============================");

        linkQueue.dequeue();                       //删除两个数据

        linkQueue.dequeue();

        System.out.println("===========traverse===========");

        linkQueue.traverse();                      //删除后再打印

        System.out.println("==============================");

        System.out.println(linkQueue.size());   //打印个数是否正确

    }

}

14java虚拟机的内存分区和作用:

1、程序计数器:(保存当前程序执行指令的地址PC寄存器

用来指示执行哪条指令

通过改变计数器的值来选择下一条需要执行的字节码的指令;

程序计数器为私有的,每个线程都有单独的程序计数器,使线程能顺利来回切换;

如果线程正在执行的是java代码,程序计数器记录的是当前线程所执行字节码指令的地址;

此内存区域是唯一一个Java虚拟机规范中没有规定任何OutOfMemoryError(OOM)情况的区域。

2、java虚拟机栈:java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧用于储存局部变量表,操作栈,运行时常量池的引用,方法返回地址等。每个线程也有有单独的栈。

一个java方法从调用到执行完成,就对应着一个帧栈从虚拟机栈的入栈到出栈过程。

线程当前所执行方法的帧栈一定位于顶部;

在虚拟机栈中可能会出现两种异常:StackOverflowError和OutOfMemory

StackOverflowError:如果线程请求的栈深度大于当前虚拟机所允许的深度,会抛出该异常;

OutOfMemory:如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存,会抛出该异常

3、本地方法栈:虚拟机栈是为虚拟机执行的Java方法服务,而本地方法栈是为虚拟机使用到的Native方法服务;

和虚拟机栈一样可能回出现两种异常:StackOverflowError和OutOfMemory

4java堆:java堆是所有线程共享的一块区域,也是最大的一块,在虚拟机启动时创建;

java堆的目的是存放对象实例以及数组;数组引用是存放在栈

java堆是垃圾收集器主要管理的区域;GC堆

java堆只有一个堆,并且是共享的

5、方法区:方法区也是内存共享的一块区域。它用于存放已被虚拟机已加载的类信息、常量、静态变量、被编译后的代码;

方法区很少用于垃圾收集,一般用来收集常量池和对类型的卸载;

方法区还存在一个运行常量池,类和接口被加载到JVM时就会自动创建;

常量池主要用于存放编译时生成的字面量和符号引用,具有动态性,编译时、运行时都能将常量放入池中。

 

运行时数据区(JVM内存)主要用来储存运行时需要的数据和信息。内存管理就是对这段空间管理(如何分配和回收内存空间)

15BST的时间复杂度:

优化成尾递归,平均时间复杂度可能更少

 

平均时间复杂度

最差时间复杂度

删除

log(n)

n

插入

log(n)

n

查找啊

log(n)

n

 

BST的规则:

若任意左子树不为空,则左子树上所有节点值都小于根节点的值;

若任意右子树不为空,则右子树上所有节点值都大于根节点的值;

任意节点的左右子树也分别未二叉查找树;

没有键值相等的节点。

16BSTjava实现

遍历(前序、中序、后序)(递归/非递归)

查找(与根比较,小于就找左子树、大于就找右子树,直到相等或遍历到叶子结点未相等)

删除(判断树是否为空,删除结点的左右子树都为空、左子树为空或右子树为空、左右子树都不为空)

插入(判断树是否为空,与根比较,小于插入到左子树、大于插入到右子树,直到找到空位则插入或相等则插入失败)

最小关键字结点(最左边结点,从根开始查找左子树,直到没有左结点)

最大关键字结点(最右边结点,从根开始查找右子树,直到没有右结点)

前驱(该结点的左子树中的最大结点)(如果有左孩子,则返回左子树中的最大结点;如果没有左孩子,有两种情况:a.若它为父结点的右孩子,返回父结点;b.若它为结点的左孩子,返回它的拥有右孩子的最低父结点)

后继(该结点的右子树种的最小结点)(如果有右孩子,则返回右子树中的最小结点;如果没有右孩子,有两种情况:a.若它为父结点的左孩子,返回父结点;b.若它为结点的右孩子,返回它的拥有左孩子的最低父结点)

销毁(若树为空,返回空;若左子树不为空,递归左子树;若右子树不为空,递归右子树;最后将根置空)

17、数据结构 求图的两点的最短路径

(迪斯科拉算法):单源最短路径算法

算法思想:按最短路径长度递增的关系,逐步产生各最短路径

以起始点为中心向外层层扩展,直到扩展到终点为止

图中不存在负权边

Dijkstras

 

 

Floyd算法:弗洛伊德):任意两点间的最短路径的一种算法。(动态规划算法

  正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。Floyd-Warshall算法的时间复杂度为O(N3),空间复杂度为O(N2)。

  检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。

 

Floyd算法过程矩阵的计算----十字交叉法
方法:两条线,从左上角开始计算一直到右下角 如下所示

如果 不在线上的元素 对应红线上两个元素之和    则改变



18java数据库连接池的实现和工作原理:

连接池:用于创建和管理数据库连接的缓冲池技术。当一个线程需要使用JDBC对数据库进行操作时,会向池中请求一个连接,当使用完毕后返回连接池中。

优点:1、减少连接创建时间;(减少连接创建的次数和时间)

      2、简化编程模式;(每个线程都能像创建自己的JDBC连接的操作一样)

      3、控制资源的使用(每次访问都连接会造成资源浪费和高负载异常)

一个数据库连接池;一套连接使用、管理、分配的策略;

工作原理:(三部分)

1、连接池的建立;(在池中创建几个连接对象)

2、连接池的管理;核心。请求连接时,查看是否有空闲连接,若有则直接分配,若没有则查看连接是否已经达到最大上限,如果没有达到就创建一个,如果达到就按照最大等待时间等待,如果等待时间超过后就抛出异常。

3、连接池的关闭:当程序退出时,关闭连接池中所有连接,释放连接池相关资源;

连接池关键问题分析:

1、并发问题(多线程环境)

2、多数据库服务器和多用户(连接不同数据库,用单例模式的连接池管理类)(多用户可以使用在资源文件中设置多个相同url链接)

3、事务处理(事务的原子性,采用每一个事务独占一个连接来实现

4、连接池的分配和释放

5、连接池的配置和维护(连接池到底放多少个连接比较合适,最小连接数,最大连接数,动态的话每隔一段时间就对连接池进行检测)

连接池的实现:

连接池类:

从连接池获取或创建可用连接;

使用完毕之后,把连接返还给连接池;

在系统关闭前,断开所有连接并释放连接占用的系统资源;

还能够处理无效连接

public class DBConnectionPool implements TimerListener{

private int checkedOut;      //已被分配出去的连接数

private ArrayList freeConnections=new ArrayList();

//容器,空闲池,根据//创建时间顺序存放已创建但尚未分配出去的连接

private int minConn;       //连接池里连接的最小数量

private int maxConn;       //连接池里允许存在的最大连接数

private String name;       //为这个连接池取个名字,方便管理

private String password;    //连接数据库时需要的密码

private String url;         //所要创建连接的数据库的地址

private String user;        //连接数据库时需要的用户名

public Timer timer;        //定时器

    public DBConnectionPool(String name,String URL,String user,

String password,int maxConn)  //公开的构造函数

 

public synchronized void freeConnection(Connection con)

                          //使用完毕之后,把连接返还给空闲池

public synchronized Connection getConnection(long timeout)

                          //得到一个连接,timeout是等待时间

public synchronized void release()

                          //断开所有连接,释放占用的系统资源

private Connection newConnection()

                          //新建一个数据库连接

public synchronized void TimerEvent()

                          //定时器事件处理函数

}

 

 

 

连接池管理类:(单例模式)只有一个管理类

装载并注册特定数据库的JDBC驱动程序;

根据属性文件给定的信息,创建连接池对象;

为方便管理多个连接池对象,为每一个连接池 对象取一个名字,实现连接池名字与其实例之间的映射;

跟踪客户使用连接情况,以便需要是关闭连接释放资源。

public class DBConnectionManager {

static private DBConnectionManager instance;

                          //连接池管理类的唯一实例

static private int clients;       //客户数量

private ArrayList drivers=new ArrayList();

                          //容器,存放数据库驱动程序

private HashMap pools = new HashMap();

             //name/value的形式存取连接池对象的名字及连接池对象

static synchronized public DBConnectionManager getInstance()

/*如果唯一的实例instance已经创建,直接返回这个实例;否则,调用私有构造函数,创建连接池管理类的唯一实例*/

private DBConnectionManager()

                       //私有构造函数,在其中调用初始化函数init()

public void freeConnection(String name,Connection con)

                       //释放一个连接,name是一个连接池对象的名字

public Connection getConnection(String name)

                       //从名字为name的连接池对象中得到一个连接

public Connection getConnection(String name,long time)

        //从名字为name的连接池对象中取得一个连接,time是等待时间

public synchronized void release()   

                //释放所有资源

private void createPools(Properties props)

                //根据属性文件提供的信息,创建一个或多个连接池

private void init()

                //初始化连接池管理类的唯一实例,由私有构造函数调用

private void loadDrivers(Properties props)   //装载数据库驱动程序

}

开源数据连接池:

1、Dbcp

   使用最多,配置方便,更多应用于开源和tomcat应用例子;

   稳定性可以,速度稍慢,在大量并发压力下稳定性下降。

   不提供连接池监控

2、C3p0

   持续运行稳定性可以,在大量并发压力下稳定性也有一定保证;

   不提供连接池监控。

3、Proxool

   稳定性有一定问题;

   提供连接池监控。(有助于确定是否有连接没有被关掉,可以排除一些代码的性能问题)

商业中间件连接池:

1、weblogic的连接池功能强大,使用配置,监控配置简明

   连接池的持续运行的稳定性很强,在大并发量的压力下性能也相当优秀

   能够连接池监控;

   在一些异常情况下连接池里的连接也能够及时释放

2、websphere的连接池功能强大,使用复杂,选项功能更多

   连接池的持续运行的稳定性很强,在大并发量的压力下性能也相当优秀

   能够连接池监控;

   在一些异常情况下连接池里的连接也能够及时释放

 

为啥最大连接数会有限制?开源100;商业200/300

  答:应用服务器维护连接需要一定内存支持,维护连接对CPU有不小的负载

 

连接数为啥不能太小?

  答:太小系统速度慢,性能上有压力,用户体验差。

19、二分查找的实现:

非递归:

·   public static int binarySearch(int[] srcArray, int des){   

·          int low = 0;   

·          int high = srcArray.length-1;   

·          while(low <= high) {   

·              int middle = (low + high)/2;   

·              if(des == srcArray[middle]) {   

·                  return middle;   

·              }else if(des <srcArray[middle]) {   

·                  high = middle - 1;   

·              }else {   

·                  low = middle + 1;   

·              }  

·          }  

·          return -1;  

·   }

 

递归:

· public static int binarySearch(int[] dataset,int data,int beginIndex,int endIndex){    

·         int midIndex = (beginIndex+endIndex)/2;    

·         if(data <dataset[beginIndex]||data>dataset[endIndex]||beginIndex>endIndex){  

·             return -1;    

·         }  

·         if(data <dataset[midIndex]){    

·             return binarySearch(dataset,data,beginIndex,midIndex-1);    

·         }else if(data>dataset[midIndex]){    

·             return binarySearch(dataset,data,midIndex+1,endIndex);    

·         }else {    

·             return midIndex;    

·         }    

·   } 

20java实现全排列:

public class Permutation {

    public static void permulation(int[] list, int start, int length) {

        int i;

        if (start == length) {

            for (i = 0; i < length; i++)

                System.out.print(list[i] + " ");

            System.out.println();

        } else {

            for (i = start; i < length; i++) {

                swap(list, start, i);

                permulation(list, start + 1, length);

                swap(list, start, i);

            }

        }

    }

    public static void swap(int[] list, int start, int i) {

        int temp;

        temp = list[start];

        list[start] = list[i];

        list[i] = temp;

    }

    public static void main(String[] args) {

        int length = 3;

        int start = 0;

        int list[] = new int[length];

        for (int j = 0; j < length; j++)

            list[j] = j + 1;

        permulation(list, start, length);

    }

}

 

移到哪位就和首位先交换:递归之前先将需要排列的首位移动的首位交换。

abc

第一轮,a和a交换,然后递归 bc

第二轮,a和b交换,然后递归ac

第三轮,a和c交换,然后递归bc

21Javavolatile的作用

对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的

volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 

1、锁的两个特性:互斥性 可见性

互斥性:一次只允许一个线程持有某个特定的锁,这样一次就只有一个线程能够使用该共享数据。

可见性:确保释放锁之前对共享数据的更改对于下一个获取该锁的另一个线程是可见的。

volatile具有synchronized的可见特性,但不具有原子性。线程能够自动发现 volatile 变量的最新值

2、正确使用volatile变量的条件:

使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

· 对变量的写操作不依赖于当前值。x++

· 该变量没有包含在具有其他变量的不变式中。“start <=end”

单独使用 volatile 还不足以实现计数器互斥锁或任何具有与多个变量相关的不变式的类

3、使用volatile变量而不使用锁的原因:(优点)

1)简易性:更易于编码和阅读

2)可伸缩性:volatile变量不会像锁那样造成线程阻塞读多于写的操作时,相对于锁,volatile 变量通常能够减少同步的性能开销

3)性能volatile 变量同步机制的性能要优于锁

 

总结:volatile相对于synchronized和锁,不具备原子性更容易出错,安全性相对差,但开销小。

22、动态代理:

AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理

jdk动态代理是由java内部的反射机制实现的;

Cglib动态代理底层是借助asm实现的。

反射机制是在生成类过程比较高效,asm是在生成类后的相关执行过程比较高效。

jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用

定义接口和实现:

·  public interface UserService {  

·      public String getName(int id);  

·    

·      public Integer getAge(int id);  

·  }  

 

·  public class UserServiceImpl implements UserService {  

·      @Override  

·      public String getName(int id) {  

·          System.out.println("------getName------");  

·          return "Tom";  

·      }  

·    

·      @Override  

·      public Integer getAge(int id) {  

·          System.out.println("------getAge------");  

·          return 10;  

·      }  

·  } 

jdk动态代理:通过接口

·  import java.lang.reflect.InvocationHandler;  

·  import java.lang.reflect.Method;  

·  public class MyInvocationHandler implements InvocationHandler {  

·      private Object target;  

·    

·      MyInvocationHandler() {  

·          super();  

·      }  

·    

·      MyInvocationHandler(Object target) {  

·          super();  

·          this.target = target;  

·      }     

·      @Override  

·      public Object invoke(Object o, Method method, Object[] args) throws Throwable {  

·          if("getName".equals(method.getName())){  

·              System.out.println("++++++before " + method.getName() + "++++++");  

·              Object result = method.invoke(target, args);  

·              System.out.println("++++++after " + method.getName() + "++++++");  

·              return result;  

·          }else{  

·              Object result = method.invoke(target, args);  

·              return result;  

·          }    

·      }  

·  }  

 

·  import com.meituan.hyt.test3.service.UserService;  

·  import com.meituan.hyt.test3.service.impl.UserServiceImpl;    

·  import java.lang.reflect.InvocationHandler;  

·  import java.lang.reflect.Proxy;  

·  public class Main1 {  

·      public static void main(String[] args) {  

·          UserService userService = new UserServiceImpl();  

·          InvocationHandler invocationHandler = new MyInvocationHandler(userService);  

·          UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),  

·                  userService.getClass().getInterfaces(), invocationHandler);  

·          System.out.println(userServiceProxy.getName(1));  

·          System.out.println(userServiceProxy.getAge(1));  

·      }  

·  }  

cglib动态代理的实现:通过类继承

·  import net.sf.cglib.proxy.MethodInterceptor;  

·  import net.sf.cglib.proxy.MethodProxy;    

·  import java.lang.reflect.Method;  

·  public class CglibProxy implements MethodInterceptor {  

·      @Override  

·      public Object intercept(Object o, Method method, Object[] args, MethodProxy methodPro    xy) throws Throwable {  

·          System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");  

·          System.out.println(method.getName());  

·          Object o1 = methodProxy.invokeSuper(o, args);  

·          System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");  

·          return o1;  

·      }  

·  }

 

·  import com.meituan.hyt.test3.service.UserService;  

·  import com.meituan.hyt.test3.service.impl.UserServiceImpl;  

·  import net.sf.cglib.proxy.Enhancer;   

·  public class Main2 {  

·      public static void main(String[] args) {  

·          CglibProxy cglibProxy = new CglibProxy();  

·          Enhancer enhancer = new Enhancer();  

·          enhancer.setSuperclass(UserServiceImpl.class);  

·          enhancer.setCallback(cglibProxy);  

·          UserService o = (UserService)enhancer.create();  

·          o.getName(1);  

·          o.getAge(1);  

·      }  

·  } 

生成二进制字节码过程:在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码然后再把二进制数据加载转换成对应的类

JVM虚拟机工作:JVM虚拟机读取字节码文件,取出二进制数据,加载进内存中,解析.class文件的信息,最后生成对应的class对象。

java字节码的生成两种开源框架;ASM;javassist

ASM

1、ASM可以直接产生二进制文件,也可以在类被加载进虚拟机前动态改变类得到行为;

2、ASM的操作级别是底层JVM的汇编指令级别,这要求对.class组织结构和JVM汇编指令有了解

3、在代码里生成字节码,并动态加载成类,最后创建实例。

Javassist

1、javassist是一个开源的分析、编辑和创建字节码的类库;

2、直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类

代理的基本构成:

静态代理:

 

 

Subject角色负责定义RealSubject和Proxy角色应该实现的接口

RealSubject角色用来真正完成业务服务功能;

Proxy角色负责将自身的Request请求调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。

静态代理优点:

访问无法访问的资源,增强现有接口业务方面的功能比较方便。

缺点:

1)系统内的类规模增大,并不易于维护;

2)Proxy只是起到了中介的作用,这种代理容易导致系统结构比较臃肿和松散。

 

proxy角色在执行代理的时候,无非是在调用真正业务之前或之后进行“额外”业务。

 

为了构造通用性和简单性的代理类,将所有触发的真实角色动作交给一个触发的管理器,这个管理器将统一管理触发。这种管理器就是Invocation Handler

proxy动态代理:

在运行状态中,需要代理的地方,根据Subject RealSubject,动态地创建一个Proxy,用完之后,就会销毁

动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处 理,而InvocationHandler则调用具体对象角色的方法

 

 

约定Proxy RealSubject可以实现相同的功能,有两种方式:

a.一个比较直观的方式,就是定义一个功能接口,然后让ProxyRealSubject来实现这个接口。JDK代理

b.还有就是通过继承。因为如果Proxy继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法,来实现多态。cglib代理

 

JDK通过 java.lang.reflect.Proxy包来支持动态代理

newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

 

对于InvocationHandler,我们需要实现下列的invoke方法:

invoke(Object proxy,Method method,Object[] args)
在调用代理对象中的每一个方法时,在代码内部,都是直接调用了InvocationHandler 的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法。

JDK生成动态代理的特点:

1.继承自 java.lang.reflect.Proxy,实现了 Rechargable,Vehicle 这两个ElectricCar实现的接口;

2.类中的所有方法都是final 的;

3.所有的方法功能的实现都统一调用了InvocationHandler的invoke()方法。

 

JDK主要会做以下工作:

    1.   获取 RealSubject上的所有接口列表;
    2.   确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX

    3.   根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码;

    4 .  将对应的字节码转换为对应的class对象;

    5.   创建InvocationHandler实例handler,用来处理Proxy所有方法调用;

    6.   Proxy class对象 以创建的handler对象为参数,实例化一个proxy对象

cglib 创建某个类A的动态代理类的模式是:

  1.   查找A上的所有非finalpublic类型的方法定义;

  2.   将这些方法的定义转换成字节码;

  3.   将组成的字节码转换成相应的代理的class对象;

  4.   实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

23JAVA垃圾回收机制(GC

一个对象在堆内存中的三种状态;

1、激活状态:对象被创建后,一个及以上的变量引用它。

2、去活状态:程序中某个对象不再有变量引用,进入去活状态,还可以通过调用finalize重新让一个变量引用该对象,重新进入激活状态。

3、死亡状态:该对象和所有引用变量的关联都被切断,用finalize方法也没法进入激活状态,将永久没法被引用。

a.当对象永久的失去引用后,系统会在合适的时候回收它所占用的内存

b.垃圾回收机制只负责回收内存中的对象,不会回收任何物理资源

c.除了释放没用对象,垃圾回收还会清除内存碎片

垃圾回收可以有效地防止内存泄露,有效地使用空闲内存;

内存泄露:内存在使用完毕后没有被回收

java的内存泄露:一个内存对象的生命周期超出程序需要它的时间长度

垃圾回收机制要做的两件事:1)发现没有被引用的对象 2)回收该对象的占用的内存

垃圾回收机制的算法:

1、引用计数法:

优点:引用计数器可以很快执行,对程序不会被长时间打断的实时环境有利。

缺点:无法检测出循环引用

2、标志清除算法:

从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

java中可作为GC ROOT对象:

1)虚拟机中的引用对象

2)方法区中静态引用对象

3)方法区中的常量引用对象

4)本地方法栈中的引用对象(native对象)

从根集合进行扫描,对存活的对象进行标志,标志完毕后,再扫描整个空间中未标志的对象,进行回收。容易造成内存碎片

3、标志整理算法:

跟标志清除算法一样处理,但是在清除后,对左端空闲空间移动,并更新对应的指针。

成本高,但是解决了内存碎片的问题。

4、copying算法

该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收

5、generation算法

不同生命周期的对象可以采取不同的回收算法,以便提高回收效率

年轻代、年老代、持久代

新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge

老年代收集器使用的收集器:Serial Old、Parallel Old、CMS

Minor GC ,Full GC 触发条件

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件

1)调用System.gc时,系统建议执行Full GC,但是不必然执行

2)老年代空间不足

3)方法去空间不足

4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

javaGC同样会有内存泄漏:

内存泄漏的几种情况:

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

2各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。

3监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

 

垃圾回收机制的优点:

1)提高编程效率;

2)保护程序完整性,提高安全性

缺点:开销大影响性能

使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收

触发GC的条件:

1当应用程序空闲时,即没有应用线程在运行时,GC会被调用

2Java堆内存不足时,GC会被调用

24、基本类型(原始类型)和包装类(引用类型)的区别:

1、基本类型不需要new来创建,对象直接存放在堆栈中,这样使用更高效;包装类对象需要new来创建,并存放在堆中,并通过堆栈中的引用来使用对象。

int i = 5;//直接在栈中分配空间 
Integer i = new Integr(5);//对象是在堆内存中,而i(引用变量)是在栈内存中 

2、基本类型不具有对象的类型;包装类就是对象类型,在使用集合类型collection时就必须要用包装类。相当于将基本类型包装起来。

3、基本类型的初始值为0或false(非null),包装类的初始值为null。

4、基本类型只包含数据,包装类包含数据和操作。

包装类和基本类型的相互转换:

包装类->基本类型:直接赋值;

基本类型->包装类:以基本类型为参数new一个。

 

堆中分配空间需要的时间远大于栈中分配空间的时间,所以java的速度比c慢。但是需要用到集合类时,基本类型是放不进去的。

25HashMapHashSet的实现原理:

HashMap:

1、HashMap实现于Map接口,Map键值对映射,Map键值不允许有重复值,但是键和值允许为null。

2、Map有两种实现方式:HashMap和TreeMap。

HashMap是无序的,TreeMap保存了元素的次序,是有序的。

3、HashMap是非synchronized的,所以是线性不安全的,但速度很快。

4、 HashMap是基于hashing原理,使用put(key,value)存储对象到HashMap中,使用get(key)将对象从HashMap取出。当调用put方法传递键和值时,先对键调用HashCode方法,用于找到bucket位置来储存Entry对象

5、HashMap是在bucket中储存键对象和值对象,作为Map.Entry而不是仅仅储存值。

6、当两个对象的HashCode相同时,它的buckey位置相同,会发生“碰撞”。解决方法:因为HashMap使用链表储存对象,所以Map.Entry会储存在链表中。

7、在使用get方法时,如果两个键的HashCode相同,是如何获取值的 ?答:当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置如果该buckey储存两个值对象,则调用key.equals()方法去找链表中正确的节点,最终找到值对象。

8、怎么减少“碰撞”的发生?答:使用不可变的,声明为final的对象,并结合采用合适的hashcode和equals方法。

9、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?负载因子默认为0.75。将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中这个过程叫作rehashing

10、重新调整大小会发生条件竞争。因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到 新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了

HashSet:

1、HashSet实现了Set接口,它不允许集合中有重复的值

2、HashSet采用哈希算法,底层用数组存储数据。默认初始化容量16,加载因子0.75

3、重写equals()和hashCode()方法添加时,先使用hashCode()判断是否冲突,冲突时再使用equals()判断是否相等。

区别:

1、hashset仅存储对象,而hashmap存储键值对。

2、hashset使用add()方法添加元素;hashmap使用put()方法添加元素

3、HashMap中使用键对象来计算hashcode值

   HashSet使用成员对象来计算hashcode值对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false

4、HashMap的速度比HashSet会快点。

26HashMapHashTable的区别:

HashMapHashtable都实现了Map接口主要的区别有:线程安全性,同步(synchronization),以及速度。

1.HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

 

2.HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个 线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。

 

3.另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是 fail-fast的。

 

4.由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。

 

5.HashMap不能保证随着时间的推移Map中的元素次序是不变的。

27、数据库的ACID

ACID:指可靠数据库管理系统中,事务所具有的四个特性:原子性、持久性、隔离性、一致性。

原子性:事务是一个不可分割的工作单位。要么都做,要么都不做。

持久性:事务进行更改完成后,事务对数据库的更改持久保存在数据库中。

一致性:事务开始之前和事务结束之后,数据库的完整性约束没有被破坏。

隔离性:多个事务并发访问时,事务之间是隔离的。一个事务不应该影响其他事务的效果。

通过添加锁来隔离。

事务之间的影响分为:脏读、不可重复读、幻读、不可更新。

脏读:脏读可能是一个事务读取了另一个事务未提交的数据,这个数据可能会回滚。操作过程中有一步其实没做,但以为是做了,所以就停止操作。

不可重复读:在数据库访问中,一个事务在同一范围内的相同查询确得到了不同的结果。在查询时,其他事务的查询修改造成的。

幻读:当事务不是独立执行时发生的一种现象。指事务操作完成后,但是由于其他事务也对同对象操作,造成事务1操作的看到结果不是预想的。

丢失更新:两个事务同时读同一条记录,A进行修改记录,B也进行修改记录(但B不知道A修改)B提交结果后,B修改的结果覆盖了A修改的结果。相当于A修改失效。

28java线程池:

为什么要使用线程池:每次创建和销毁线程需要的时间花销和资源消耗都是很大。主要解决线程生命周期中的开销问题和资源不足问题。

 

线程池的组成部分:线程池管理器、工作线程、任务队列和任务接口。

线程管理器主要是创建、销毁和管理线程池,将工作线程放入线程池中,添加新任务;

工作线程是一个可以循环执行任务的线程,没有任务时就进行等待;

任务队列是提供一个缓冲机制,没有处理的任务放入任务队列中;

任务接口主要是规定任务的入口、任务的结束收尾工作以及任务的执行状态等。

 

四种线程池:

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

 

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的类

 

public ThreadPoolExecutor(int corePoolSize,

                              int maximumPoolSize,

                              long keepAliveTime,

                              TimeUnit unit,

                              BlockingQueue<Runnable> workQueue) {

        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

             Executors.defaultThreadFactory(), defaultHandler);

    }

 

 public ThreadPoolExecutor(int corePoolSize,

                              int maximumPoolSize,

                              long keepAliveTime,

                              TimeUnit unit,

                              BlockingQueue<Runnable> workQueue,

                              ThreadFactory threadFactory) {

        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

             threadFactory, defaultHandler);

    }

   

 public ThreadPoolExecutor(int corePoolSize,

                              int maximumPoolSize,

                              long keepAliveTime,

                              TimeUnit unit,

                              BlockingQueue<Runnable> workQueue,

                              RejectedExecutionHandler handler) {

        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

             Executors.defaultThreadFactory(), handler);

    }

 

 public ThreadPoolExecutor(int corePoolSize,

                              int maximumPoolSize,

                              long keepAliveTime,

                              TimeUnit unit,

                              BlockingQueue<Runnable> workQueue,

                              ThreadFactory threadFactory,

                              RejectedExecutionHandler handler) {

        if (corePoolSize < 0 ||

            maximumPoolSize <= 0 ||

            maximumPoolSize < corePoolSize ||

            keepAliveTime < 0)

            throw new IllegalArgumentException();

        if (workQueue == null || threadFactory == null || handler == null)

            throw new NullPointerException();

        this.corePoolSize = corePoolSize;

        this.maximumPoolSize = maximumPoolSize;

        this.workQueue = workQueue;

        this.keepAliveTime = unit.toNanos(keepAliveTime);

        this.threadFactory = threadFactory;

        this.handler = handler;

}

corePoolSize:     核心池的大小

maximumPoolSize:  线程池最大线程数

keepAliveTime:    表示线程没有任务执行时最多保持多久时间会终止

unit:             参数keepAliveTime的时间单位

workQueue:        一个阻塞队列

threadFactory:    线程工厂,主要用来创建线程;

handler:          表示当拒绝处理任务时的策略

 

ThreadPoolExecutor 继承了AbstractExecutorService类,并提供了四个构造器

AbstractExecutorService是一个抽象类,它实现了ExecutorService接口

ExecutorService又是继承了Executor接口

 

 

public interface Executor {

    void execute(Runnable command);

}

 

ThreadPoolExecutor中的四种方法:

execute()向线程池提交一个任务,交由线程池去执行。

submit()用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果

shutdown()关闭线程池

shutdownNow()关闭线程池

 

1、线程池状态:

volatile int runState;

static final int RUNNING    =0;

static final int SHUTDOWN   =1;

static final int STOP       =2;

static final int TERMINATED =3;

runState表示线程目前的状态,用volatile表示状态的可见性。

1当创建线程池后,初始时,线程池处于RUNNING状态;

2如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

3如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

4当线程池处于SHUTDOWNSTOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态

2、任务执行

3、线程池中的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

prestartCoreThread():初始化一个核心线程;

prestartAllCoreThreads():初始化所有核心线程

4、任务缓存队列及排队策略

  workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

(1).ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

(2).LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

(3).synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

5、任务拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略

6、线程池的关闭

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

7、线程池的动态调整

setCorePoolSize()和setMaximumPoolSize(),

setCorePoolSize:设置核心池大小

setMaximumPoolSize:设置线程池最大能创建的线程数目大小

29、解决哈希冲突的四种方法:

1.开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)

 

线性探测再散列求余,算出的地址有人,就往下跳一位,再有人再跳,知道有空位。
2.再哈希法

这种方法是同时构造多个不同的哈希函数
3.链地址法(Java hashmap就是这么做的)


4.建立一个公共溢出区

将哈希表分为基本表溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表

30Object类中有哪些方法:

Clone()

保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常

Equals()

Object中与==是一样的,子类一般需要重写该方法

hashCode()

该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到

getClass()

final方法,获得运行时类型

Wait()

使当前线程等待该对象的锁

Notify()

唤醒在该对象上等待的某个线程

notifyAll()

唤醒在该对象上等待的所有线程

toString()

转换成字符串,一般子类都有重写,否则打印句柄

31、内存溢出的解决方法

常见原因:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

 

2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

 

3.代码中存在死循环或循环产生过多重复的对象实体;

解决方法:

1.把启动参数内存值设置足够大;

2.检查代码是否有死循环或递归操作

3.是否有循环重复产生大量新对象实体

4.检查数据库的查询是否查询获取的数据过大

5.检查是否有list、map集合有使用完没有清除

6.在程序中多使用strcpy_s、memcpy_s等具有缓冲区大小检查的函数,去取代strcpy、memcpy等

7.对代码先进行单元测试

8.使用检查工具

A)BoudsChecker,除了能够检查内存泄露,也能检查内存溢出问题

B)AppVerifier,专门用来检测那些用普通方法检测不出的意想不到的bug(比如内存溢出、错误句柄使用等)

32String 转出 int型,如何判断能不能转?怎么转?

使用bool  isNumber (string str) 方法判断是不是数值类型,如果是的话可以转,否则不可以。

怎么转有两种方法:

第一种方法:i=Integer.parseInt(s);//直接使用静态方法,不会产生多余的对象,但会抛出异常
第二种方法:i=Integer.valueOf(s).intValue();//Integer.valueOf(s)相当于new Integer(Integer.parseInt(s)),也会抛异常,但会多产生一个对象

33、多线程的原理:

进程是一个正在运行的程序;

线程是一个执行单元;一个进程可以有多个线程并发。

线程的五种状态:创建、就绪、运行、堵塞、死亡

实现多线程的方式:

1.继承Thread类

2.实现Runnable接口

多线程并发,是看上去是同时,实际是线程切换时间很短。

每个线程具有自己的堆栈和自己的 CPU 寄存器副本文件、静态数据和堆内存 由进程中的所有线程共享

多线程最大限度地利用CPU资源,增加效率,时间分片(时间不一定相等)使用。

线程调度是值按照特定的机制为多个线程分配CPU的使用权。

调度的模式有两种:分时调度和抢占式调度

34、数据仓库的原理:

数据仓库:一个面向主题的、集成的、非易失性的、随时间变化的数据集合,用于支持管理层的决策过程。

面向主题:由于数据仓库的用户都是企业决策的管理者,这些人面对的往往都是比较抽象的、层次较高的管理分析对象。例如企业中的客户、产品、供应商都可以作为主题看待。

集成的:将分散于各处的源数据进行抽取、筛选、清理、综合等工作,使数据仓库的数据具有集成性。

非易失性:数据仓库的数据不进行更新处理。一旦进入数据仓库后就会保持很长一段时间。

随时间变化性:数据随着时间的推移而变化。

 

数据仓库和数据库的比较:

 

两类数据处理:

操作型数据处理,是指对数据库联机的日常操作,主要完成数据的收集、整理、存储以及增删查改等操作,主要由一般工作人员和基层管理人员完成。
分析型数据处理,是指对数据的再加工,通常是对海量的历史数据查询和分析,从中获取信息,主要由分析人员和中高级管理人员完成。

两类数据:

 

数据库的局限性:

1.数据的分散

2.数据的不一致问题

3.历史数据问题

4.数据粒度问题

 

数据库和数据仓库分工不同,数据库存放操作型数据,用于操作型数据处理,关注的事务处理的效率;数据仓库存放分析型数据,用于分析型数据处理,关注的是分析和查询的效率;两者功能不同、用途不同,因此结构也会不同。

35map有哪些种类:

Map有:HashMap、TreeMap、Hashtable。
    1、HashMap:线程不安全,键、值不允许为null,并且没顺序。
    2、Hashtable:线程安全键、值允许为null,并且没顺序。
    3、TreeMap:线程不安全、键、值不允许为null,并且没顺序用于排序

HashMap与其他几种类型的比较:

LinkedHashMap:
    它的父类是HashMap,使用双向链表来维护键值对的次序,迭代顺序与键值对的插入顺序保持一致。LinkedHashMap需要维护元素的插入顺序,so性能略低于HashMap,但在迭代访问元素时有很好的性能,因为它是以链表来维护内部顺序。

WeakHashMap

    WeakHashMap与HashMap的用法基本相同,区别在于:后者的key保留对象的强引用即只要HashMap对象不被销毁,其对象所有key所引用的对象不会被垃圾回收HashMap也不会自动删除这些key所对应的键值对对象。但WeakHashMap的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被回收。WeakHashMap中的每个key对象保存了实际对象的弱引用,当回收了该key所对应的实际对象后,WeakHashMap会自动删除该key所对应的键值对。

IdentityHashMap:
    IdentityHashMap与HashMap基本相似,只是当两个key严格相等时,即key1==key2时,它才认为两个key是相等的 。IdentityHashMap也允许使用null,但不保证键值对之间的顺序。

36hashmap迭代的三种方式:

 

//定义一个HashMap变量

HashMap<String, String> emails = new HashMap<String, String>();  

 

//方法一: 用entrySet(),效率高 

Iterator it = emails.entrySet().iterator();  

while(it.hasNext()){  

   Map.Entry m=(Map.Entry)it.next();  

   System.out.println("email-" + m.getKey() + ":" + m.getValue());  

}  

 

// 方法二:直接再循环中  

for (Map.Entry<String, String> m : emails.entrySet()) {  

   System.out.println("email-" + m.getKey() + ":" + m.getValue());  

}  

 

// 方法三:用keySet(),效率低

Iterator it = emails.keySet().iterator();  

while (it.hasNext()){  

  String key=(String)it.next();  

  System.out.println("email-" + key + ":" + emails.get(key));

}

 

37equals和“=”的区别

  ==操作比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同。
  equals操作表示的两个变量是否是对同一个对象的引用,即堆中的内容是否相同

38、寻找第K小的数,解释复杂度。

1.同时找到最大值、最小值算法:

通过成对的处理元素,先将一对输入元素比较,找到较大值和较小值。然后将较大值与当前最大值比较,较小值与当前最小值比较,这样每两个元素需要比较3次,一共需要3*[n/2]次比较可以通过递归实现。

2.最坏情况下如何利用n+-2次比较找到n个元素中的第2小的元素?

答:对数组a[1…n]中元素成对的做比较,每次比较后讲较小的数拿出,形成的数组再继续这样处理,直到剩下最后的一个,就是数组中最小的那个这样比较了n-1次。

用二叉树表示:最小的数为根节点。

 

第二小的元素在这过程中跟根节点比较过的数,这些节点有logn个。然后再从这些节点中用同样的方法求最小的数,比较了logn-1次。所以总共会比较n-1+logn-1= n+logn-2次。

3.几种求第K小元素的方法:

a.普通归并排序、堆排序等。时间复杂度:nlogn

b.用大小为k的数组存放k个元素,然后求得其中的最大元素max。时间:k。剩下n-k个数,依次遍历将其与max比较,如果小于max则,将其与max替换又重新从k中选出最大元素max。最后得到的max为第k小元素。时间复杂度:nk。

c.建堆。建k个元素的最大堆。将n-k个元素与堆顶元素比较,如果小于则替换并调整堆结构。最后堆顶元素为第k小元素。建堆时间:k。对堆调整时间logk。总时间复杂度:

k+(n-k)logk

d.类快速排序法。随机选一个数s将n个数分为两部分sa sb如果sa的长度为k则直接返回,否则根据大小情况继续递归。

 

39、索引的概念及特点;
索引就类似于书的目录,不用翻阅整本书而能快速地查找到需要的信息。加快检索表的方法。

索引的特点:

1.加快数据库的检索数据

2.降低了数据库的插入、删除、修改等维护的速度

3.索引只能创建在表上,不能创建在视图上。

4.可以在优化隐藏中使用索引

 

优点:

1.通过创建唯一性索引保证数据库表中每一行数据的唯一性

2.加快数据的检索速度

3.加快表之间的连接

4.减少查询中排序和分组的时间

5.使用索引进行优化隐藏,提高系统性能

 

缺点:

1.创建索引和维护索引需要消耗大量时间,随着数据量的增加而增加

2.索引需要占物理空间

3.对表中数据进行插入、删除、修改等维护时,索引也要对应动态维护,降低了数据的维护速度。

 

索引的分类:

1.直接创建索引和间接创建索引
    直接创建索引: CREATE INDEX mycolumn_index ON mytable (myclumn)
    间接创建索引:定义主键约束或者唯一性键约束,可以间接创建索引
2.普通索引和唯一性索引
    普通索引:CREATE INDEX mycolumn_index ON mytable (myclumn)
    唯一性索引:保证在索引列中的全部数据是唯一的

CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
3.单个索引和复合索引
    单个索引:即非复合索引
    复合索引:又叫组合索引,在索引建立语句中同时包含多个字段名,最多16个字段
    CREATE INDEX name_index ON username(firstname,lastname)
4.聚簇索引和非聚簇索引(聚集索引,群集索引)
   聚簇索引:物理索引,与基表的物理顺序相同,数据值的顺序总是按照顺序排列
    CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITH
    ALLOW_DUP_ROW(允许有重复记录的聚簇索引)
   非聚簇索引:CREATE UNCLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)

什么时候使用索引?

1.当字段数据更新频率较低,查询使用频率较高并且存在大量重复值是建议使用聚簇索引
2.经常同时存取多列,且每列都含有重复值可考虑建立组合索引

40、使用linux命令求得一个文件中ip/字符串出现的次数:

1.比如request.log文件里“http:\\127.0.0.1:8080\index.jsp”,“http:\\127.0.0.1:8080\index.jsp”“http:\\127.0.0.1:8080\index.jsp”找到http这个词出现的次数。

答:grep -o 'http' request.log|wc -l        //-c只能匹配一行

2.有一个文件ip.txt,每行一条ip记录,共若干行,下面哪个命令可以实现“统计出现次数最多的前3个ip及其次数”?

答:sort ip.txt | uniq -c | sort -rn | head -n 3

首先sort进行排序,将重复的行都排在了一起,然后使用uniq -c将重复的行的次数放在了行首并去掉重复的行,在用sort -rn进行反向排序,这样就按照重复次数从高到低进行了排列,最后利用head -n 3输出行首的三行。

 

3.开始写脚本: 

 [root@qunar logs]# cat a.sh 
#!/bin/bash

#28/Jan/2015全天的访问日志放到a.txt文本
cat access.log |sed -rn '/28\/Jan\/2015/p' > a.txt 

#统计a.txt里面有多少个ip访问
cat a.txt |awk '{print $1}'|sort |uniq > ipnum.txt

#通过shell统计每个ip访问次数
for i in `cat ipnum.txt`
do 
iptj=`cat  access.log |grep $i | grep -v 400 |wc -l`
echo "ip地址"$i"2015-01-28日全天(24小时)累计成功请求"$iptj"次,平均每分钟请求次数为:"$(($iptj/1440)) >> result.txt
done

执行脚本: 

 [root@qunar logs]#sh a.sh  

 

查看结果: 

 [root@qular logs]# cat result.txt 
 ip地址10.72.14.49在2015-01-28日全天(24小时)累计成功请求0次,平均每分钟请求次数为:0
ip地址10.72.32.136在2015-01-28日全天(24小时)累计成功请求0次,平均每分钟请求次数为:0
ip地址211.151.239.39在2015-01-28日全天(24小时)累计成功请求6409763次,平均每分钟请求次数为:4451
ip地址211.151.239.41在2015-01-28日全天(24小时)累计成功请求6412232次,平均每分钟请求次数为:4452
ip地址211.151.239.42在2015-01-28日全天(24小时)累计成功请求7440次,平均每分钟请求次数为:5
ip地址211.151.239.44在2015-01-28日全天(24小时)累计成功请求7494次,平均每分钟请求次数为:5
ip地址211.151.239.45在2015-01-28日全天(24小时)累计成功请求7533次,平均每分钟请求次数为:5
ip地址211.151.239.46在2015-01-28日全天(24小时)累计成功请求6413230次,平均每分钟请求次数为:4453
ip地址211.151.239.51在2015-01-28日全天(24小时)累计成功请求7596次,平均每分钟请求次数为:5
ip地址59.151.44.181在2015-01-28日全天(24小时)累计成功请求1410次,平均每分钟请求次数为:0
ip地址59.151.44.182在2015-01-28日全天(24小时)累计成功请求696387次,平均每分钟请求次数为:483
ip地址59.151.44.183在2015-01-28日全天(24小时)累计成功请求54次,平均每分钟请求次数为:0
ip地址59.151.44.184在2015-01-28日全天(24小时)累计成功请求809次,平均每分钟请求次数为:0
ip地址59.151.44.185在2015-01-28日全天(24小时)累计成功请求38次,平均每分钟请求次数为:0

 

41重入锁、对象锁、类锁的关系

对象锁也是方法锁;

1、对象锁:synchronized修饰方法或代码块。

当对象调用同步方法或进入同步区域时,就必须获取对象锁。

每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突

java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,

public class Test {
// 对象锁:(方法锁) 
public synchronized void Method1() {
System.out.println(“我是对象锁也是方法锁”);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}

}

}

2、类锁:synchronized 修饰静态的方法或代码块

静态方法和静态变量在内存中都只有一份。所以,一旦一个静态方法明为synchronized。此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。

 

 

 

public class Test {

// 类锁:
public static synchronized void Method1() {
System.out.println("我是类锁一号");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}

}

}

对象锁和类锁的区别:

对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。

3、可重入锁也叫做递归锁,指的是同一线程外层函数获得锁之后内层递归函数仍然有获取该锁的代码,但不受影响。

可重入锁最大的作用是避免死锁

42、自旋锁

自旋锁采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时才能进入临界区

由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段适合使用自旋锁。

 

public class SpinLock {

  private AtomicReference<Thread> sign =new AtomicReference<>();

  public void lock(){

    Thread current = Thread.currentThread();

    while(!sign .compareAndSet(null, current)){

    }

  }

  public void unlock (){

    Thread current = Thread.currentThread();

    sign .compareAndSet(current, null);

  }

}

lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。

当有第二个线程调用lock操作时由于owner值不为空,导致循环一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。

43、栈内存溢出的原因:

原因:

1.循环、递归引起函数调用层次过深,每调用一次,函数的参数、局部变量等信息就压一次栈。
2.局部静态变量体积太大

 

解决:

1.增加栈内存数目

2.使用堆内存 (动态申请或将局部变量变成静态变量)

全局变量(局部的静态变量)存储于堆内存,内存较大,一般不会溢出

函数地址、函数参数、局部变量等信息存储于栈内存,栈内存默认大小为1M,容易溢出。

 

44、双向链表 前插、尾插、指定位插的实现。

public class DoublyLinkList {

 

//内置类-结点类

private class Data{                        

private Object obj;

private Data left = null;

private Data right = null;

 

Data(Object obj){

this.obj = obj;

}

}

private Data first = null;

private Data last = null;

 

 

//链表首插

public void insertFirst(Object obj){

Data data = new Data(obj);

if(first == null){  

last = data;  

}else{  

data.right = first;  

first.left = data;  

}  

first = data;  

}

 

 

//链表尾插

public void insertLast(Object obj){

Data data = new Data(obj);

if(first == null){

first = data;

}else{

last.right = data;

data.left = last;

}

last = data;

}

 

 

//链表指定位置插

public boolean insertAfter(Object target,Object obj){

Data data = new Data(obj);

Data cur = first;

while(cur != null){

if(cur.obj.equals(target)){

data.right = cur.right;

data.left = cur;

if(cur == last)

last = data;

else

cur.right.left = data;

cur.right = data;

return true;

}

cur = cur.right;

}

return false;

}

 

//链表删除首位

public Object deleteFirst() throws Exception{

if(first == null)

throw new Exception("empty!");

Data temp = first;

if(first.right == null){

first = null;

last = null;

}else{

first.right.left = null;

first = first.right;

}

return temp;

}

 

 

//链表删除尾位

public Object deleteLast() throws Exception{

if(first == null)

throw new Exception("empty!");

Data temp = last;

if(first.right == null){

first = null;

last = null;

}else{

last.left.right = null;

last = last.left;

}

return temp;

}

 

 

//链表删除指定位

public Object delete(Object obj) throws Exception{

if(first == null)

throw new Exception("empty!");

Data cur = first;

while(cur != null){

if(cur.obj.equals(obj)){

if(cur == last)

last = cur.left;

else

cur.right.left = cur.left;

if(cur == first)

first = cur.right;

else

cur.left.right = cur.right;

return obj;

}

cur = cur.right;

}

return null;

}

 

 

//链表打印

public void display(){

System.out.print("first -> last : ");

Data data = first;

while(data != null){

System.out.print(data.obj.toString() + " -> ");

data = data.right;

}

System.out.print("\n");

}

 

 

//主函数

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

DoublyLinkList dll = new DoublyLinkList();

dll.insertFirst(1);

dll.insertLast(3);

dll.insertAfter(1, 2);

dll.insertAfter(3, 4);

dll.insertAfter(4, 5);

dll.display();

dll.deleteFirst();

dll.display();

dll.deleteLast();

dll.display();

dll.delete(3);

dll.display();

dll.delete(2);

dll.display();

dll.delete(4);

dll.display();

}

}

 

45、抽象类和接口的区别:

1、抽象类

包含抽象方法的类都是抽象类,没有抽象方法但是用abstract修饰的也是抽象类。

抽象类可不止含有抽象方法,还有普通成员变量和成员方法。

抽象类和普通类的主要有三点区别:

1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。

2)抽象类不能用来创建对象;

3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

2、接口

interface定义的,方法全是抽象方法的类。

接口中可含有变量,隐式定义为 public static final类型。方法隐式定义为:public abstract。

纯抽象类,里面没有实现的方法

3、区别

1)接口是抽象类的变体,抽象类有已实现的方法,接口没有。

2)抽象类的变量可以是各类型,而接口只能是public static final。

3)接口可以多继承,而抽象类不可以。

4)接口不可以含有静态代码块、静态方法,抽象类可以。

用法:

抽象类比较适用于对象固有的行为;接口比较适用于对象可延伸附加的行为(公共行为)。

4、相同点:

1)都不能实例化

2)都含有抽象方法

46TCPUDP的区别:

TCP是面向连接的可靠传输

UDP是非面向连接的不可靠传输

TCP和UDP都是传输层协议

面向连接的区别:

TCP协议连接其实就是在物理线路上创建的一条“虚拟信道”。这条“虚拟信道”建立后,在TCP协议发出FIN包之前(两个终端都会向对方发送一个FIN包),是不会释放的。正因为这一点,TCP协议被称为面向连接的协议!   


UDP协议,一样会在物理线路上创建一条“虚拟信道”,否则UDP协议无法传输数据!但是,当UDP协议传完数据后,这条“虚拟信道”就被立即注销了!因此,称UDP是不面向连接的协议!

可靠传输解释:

因为TCP协议连接时三次握手,接收端会发出ACK确认包给发送端,告诉已收到,所以丢包会有重传机制,不会造成数据丢失,也不会乱序。所以它是可靠的

UDP没有该机制,无重传机制,会发生丢包、收到重复包、乱序等情况

但是TCP拥塞控制、数据校验、重传机制的网络开销很大,不适合实时通信

对于数据精确性要求不高的状态数据以及视频数据,丢包的影响不大对实时性要求比较高的可以用UDP协议。效率高

QQ通信是使用UDP协议,但使用TCP保持在线状态当网络很差时,TCP的优势反而变成劣势。

网络特别差的时候,tcp是不可靠的因为你检测网络掉线是需要时间的,刚好你把消息发出去时,网络其它是已经掉线了,而你却还不知道,所以为了不掉消息,在应用层还要加一层消息生传机制。这样TCP就没有什么优势了,底层有重传机制,应用层还要自己做重传。

TCP:
FTP:21, Telnet:23, SMTP:25

UDP:
DNS:53, TFTP:69, SNMP:161, RIP:520

https:
http:80

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值