Java基础问题整理

1.什么是java虚拟机?为什么java被称作是平台无关的编程语言?

什么是java虚拟机?

  • Java虚拟机是一台执行java字节码文件的虚拟计算机,它拥有独立的运行机制,其运行的java字节码也未必由java语言编译而成。
    想更深入了解java虚拟机可以查看我jvm的笔记:笔记
    为什么java被称作是平台无关的编程语言?
    在这里插入图片描述
  • java语言的跨平台性,并不是指java本身跨平台,而是java源码经过编译器编译称字节码文件,字节码文件通过安装在不同平台的java虚拟机(JVM)来翻译成不同平台能识别的机器码,从而在不同的平台执行,也就是所谓的一处编译,处处运行。java的核心就是 java虚拟机,正是由于java虚拟机的存在才实现了java的跨平台性

2.JDK和JRE的区别是什么?

  • JRE: Java Runtime Environment
  • JDK:Java Development Kit
  • JRE顾名思义是java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。
  • JDK顾名思义是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
  • 如果你需要运行java程序,只需安装JRE就可以了。如果你需要编写java程序,需要安装JDK。
  • JRE根据不同操作系统(如:windows,linux等)和不同JRE提供商(IBM,ORACLE等)有很多版本
    在这里插入图片描述

3.”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

  • Static表示静态的意思,可用于修饰成员变量和成员函数,被静态修饰的成员函数可以访问静态成员,不可以直接访问非静态成员,可以通过过创建对象实例来访问非静态成员。静态是随着类的加载而加载的,因此可以直接用类进行访问。
  • 重写是子类中的方法和子类继承的父类中的方法一样(函数名,参数,参数类型,反回值类型),但是子类中的访问权限要不低于父类中的访问权限。重写的前提是必须要继承,private修饰不支持继承,因此被私有的方法不可以被重写。静态方法形式上可以被重写,即子类中可以重写父类中静态的方法。但是实际上从内存的角度上静态方法不可以被重写。

4.是否可以在static环境中访问非static变量?

  • 这里需要知道类加载的过程,类加载过程中是先加载静态变量,再加载非静态变量的,所以不可以在static环境中直接访问非静态变量;不过可以在静态环境中通过创建对象实例对非静态变量进行间接的访问。

5.Java支持的数据类型有哪些?什么是自动拆装箱?

在这里插入图片描述

  • 自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。 因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。

6.Java中的方法覆盖(Overriding)和方法重载(Overload)是什么意思?

  • Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,因为子类可以解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。如果父类的方法是private类型,那么,子类则不存在覆盖的限制,相当于子类中增加了一个全新的方法
Override注意事项:

Override是覆盖了一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现。
另外,我们在继承中也可能会在子类覆盖父类中的方法。在覆盖时要注意以下几点:

(1)覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;
(2)覆盖的方法的返回值必须和被覆盖的方法的返回值一致;
(3)覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
(4)被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
  • Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同,即参数个数或类型不同
Overload注意事项:

Overload是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,VM就
会根据不同的参数样式,来选择合适的方法执行。在重载时要注意以下几点:

(1)在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,
同一方法内的几个参数类型必须不一样,例如可以是get(int,float),但是不能为get(int,int);
(2)不能通过访问权限、返回类型、抛出的异常进行重载;
(3)方法的异常类型和数目不会对重载造成影响;
(4)对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的
话,也只是定义了一个新方法,而不会达到重载的效果。

7.Java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?

  • 当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法。
  • Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
  • Java不支持像C++中那样的复制构造方法,这个不同点是因为如果你不自己写构造方法的情况下,Java不会创建默认的复制构造方法。

8.Java支持多继承么?

  • java中的类不支持多继承(一个子类只能有一个父类),但是一个子接口可以实现多个父接口)

9.接口和抽象类的区别是什么?

  • 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
    接口用interface修饰,抽象类用abstract修饰
    接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法
    接口中的实例变量默认是final类型的,而抽象类中则不一定
    一个类可以实现多个接口,但最多只能实现一个抽象类
    一个类实现接口的话要实现接口的所有方法,而抽象类不一定
    接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象

10.在java中为什么很多人说有值传递和引用传递?引用传递的本质是什么?

  • 形参是基本数据类型:byte short int long float double boolean char
    传递数据值
  • 实参是引用数据类型:类(例如String,Integer) 接口 数组 null
    传递地址值
    特殊类型:String、包装类等对象不可变性
  • 如果知道JVM运行时数据区的结构,应该对这个问题能够有更清晰的理解:JVM笔记
    (1)堆:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在java虚拟机
    规范的描述是:所有的对象实例即数组都要在堆上分配。被所有的线程共享。
    (2)栈:是指虚拟机栈。虚拟机栈用于存储局部变量表等。局部变量表存放了编译期
    可知长度的各种基本数据类型(boolean、byte、 char、short、 int、 float、long、double) 、对象引用
    (reference 类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完, 自动释放。栈是独立的,每个
    线程包含一个栈区
    (3)方法区:又叫静态区,用于存储已被虛拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    运行时常量池都分配在 Java 虚拟机的方法区之中。被所有的线程共享。

11.进程和线程的区别是什么?

  • 进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
  • 线程(Thread) 是操作系统能够进行运算调度的最小单位。它本包含在进程之中,是进程中的实际运作单位。一条线程指的是一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 总结:
    • 进程 指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程–资源分配的最小单位。

    • 线程 系统分配处理器时间资源的基本单位,或则说进程之内独立执行的一个单元执行流。线程–程序执行的最小单位

    • 例子:例如运行360安全卫士就是一个进程,在360中进行杀毒、垃圾清理等就是线程的执行

        进程是运行中的程序,线程是进程的内部的一个执行序列
        进程是资源分配的单元,线程是执行行单元
        进程间切换代价大,线程间切换代价小
        进程拥有资源多,线程拥有资源少
        多个线程共享进程的资源
      

想要深入了解可以查看:JUC笔记

12.创建线程有几种不同的方式?你喜欢哪一种?为什么?

第一种:继承Thread类(真正意义上的线程类),是Runnable接口的实现。
第二种:实现Runnable接口
第三种:实现Callable接口
第四种:线程池方式
  • 一般情况下,常见的是第二种。
    • Runnable接口有如下好处:
      ①避免点继承的局限,一个类可以继承多个接口。
      ②适合于资源的共享

想要深入了解可以查看:JUC笔记

13.概括解释下线程的几种可用状态

  1. 新建( new ):新创建了一个线程对象。
  2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
  3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
  4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:
    (一). 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。
    (二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
    (三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
  5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
    在这里插入图片描述

14.同步方法和同步代码块的区别是什么?

  • 同步方法默认用this或者当前类class对象作为锁;
  • 同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
  • 同步方法使用关键字 synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容}进行修饰;
  • 同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

15.在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?

  • 在 java 虚拟机中, 每个对象( Object 和 class )通过某种逻辑关联监视器,每个监视器和一个对象引用相关联, 为了实现监视器的互斥功能, 每个对象都关联着一把锁. 一旦方法或者代码块被 synchronized 修饰, 那么这个部分就放入了监视器的监视区域, 确保一次只能有一个线程执行该部分的代码, 线程在获取锁之前不允许执行该部分的代码 另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案

16.什么是死锁(deadlock)

死锁 :是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去

(1) 因为系统资源不足。
(2) 进程运行推进顺序不合适。
(3) 资源分配不当等。

如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

死锁的解除与预防:
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和
解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确
定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态
的情况下占用资源。因此,对资源的分配要给予合理的规划。

17.如何确保N个线程可以访问N个资源同时又不导致死锁?

(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。(也就是破坏了死锁的第四个条件)

18.Java集合类框架的基本接口有哪些?

总共有两大接口:
	Collection 和Map 一个元素集合,一个是键值对集合;
 	其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合;
 	而ArrayList和 LinkedList 实现了List接口,HashSet实现了Set接口,这几个都比较常用; 
 	HashMap 和HashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好;
  • java.util.Collection [I]

      |—java.util.List [I]
          |—java.util.ArrayList [C]
          |—java.util.LinkedList [C]
          |—java.util.Vector [C]
              |—java.util.Stack [C]
      |—java.util.Set [I]
          |—java.util.HashSet [C]
          |—java.util.SortedSet [I]
              |—java.util.TreeSet [C]
    
  • java.util.Map [I]

      |—java.util.SortedMap [I]
          |—java.util.TreeMap [C]
      |—java.util.Hashtable [C]
      |—java.util.HashMap [C]
          |—java.util.LinkedHashMap [C]
      |—java.util.WeakHashMap [C]
    

19.为什么集合类框架中的接口没有实现Cloneable和Serializable接口?

克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。(也就是说 接口不实现,留给具体实现类实现。)

20.什么是迭代器

  • 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

  • Java中的Iterator功能比较简单,并且只能单向移动
      (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。
      注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
      (2) 使用next()获得序列中的下一个元素。
      (3) 使用hasNext()检查序列中是否还有元素。
      (4) 使用remove()将迭代器新返回的元素删除。

  • Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

21.Iterator和ListIterator的区别是什么?

Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

22.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

  • 一:快速失败(fail—fast)
    • 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加、删除),则会抛出Concurrent Modification Exception。
    • 原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果结构发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
    • 注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
    • 场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
  • 二:安全失败(fail—safe)
    • 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
    • 原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
    • 缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
    • 场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

23.Java中的HashMap的工作原理是什么?

  • hashmap是一个key-value键值对的数据结构,从结构上来讲在jdk1.8之前是用数组加链表的方式实现,jdk1.8加了红黑树,hashmap数组的默认初始长度是16,hashmap数组只允许一个key为null,允许多个value为nullhashmap的内部实现,hashmap是使用数组+链表+红黑树的形式实现的,其中数组是一个一个Node[]数组,我们叫他hash桶数组,它上面存放的是key-value键值对的节点。HashMap是用hash表来存储的,在hashmap里为解决hash冲突,使用链地址法,简单来说就是数组加链表的形式来解决,当数据被hash后,得到数组下标,把数据放在对应下表的链表中。
  • 然后再说一下hashmap的方法实现
    • put方法,put方法的第一步,就是计算出要put元素在hash桶数组中的索引位置,得到索引位置需要三步,去put元素key的hashcode值,高位运算,取模运算,高位运算就是用第一步得到的值h,用h的高16位和低16位进行异或操作,第三步为了使hash桶数组元素分布更均匀,采用取模运算,取模运算就是用第二步得到的值和hash桶数组长度-1的值取与。这样得到的结果和传统取模运算结果一致,而且效率比取模运算高
    • jdk1.8中put方法的具体步骤,先判断hashmap是否为空,为空的话扩容,不为空计算出key的hash值i,然后看table[i]是否为空,为空就直接插入,不为空判断当前位置的key和table[i]是否相同,相同就覆盖,不相同就查看table[i]是否是红黑树节点,如果是的话就用红黑树直接插入键值对,如果不是开始遍历链表插入,如果遇到重复值就覆盖,否则直接插入,如果链表长度大于8,转为红黑树结构,执行完成后看size是否大于阈值threshold,大于就扩容,否则直接结束
    • get方法就是计算出要获取元素的hash值,去对应位置取即可。
    • 扩容机制,hashmap的扩容中主要进行两部,第一步把数组长度变为原来的两倍,第二部把旧数组的元素重新计算hash插入到新数组中,在jdk1.8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二部一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。
  • hashmap大小为什么是2的幂次方
    • 在计算插入元素在hash桶数组的索引时第三步,为了使元素分布的更加均匀,用取模操作,但是传统取模操作效率低,然后优化成h&(length-1),设置成2幂次方,是因为2的幂次方-1后的值每一位上都是1,然后与第二步计算出的h值与的时候,最终的结果只和key的hashcode值本身有关,这样不会造成空间浪费并且分布均匀,如果不是2的幂次方
    • 如果length不为2的幂,比如15。那么length-1的2进制就会变成1110。在h为随机数的情况下,和1110做&操作。尾数永远为0。那么0001、1001、1101等尾数为1的位置就永远不可能被entry占用。这样会造成浪费,不随机等问题。

24.hashCode()和Equals()的重要性体现在哪里

  • hashcode和equals组合在一起确定元素的唯一性。
  • 查找元素时,如果单单使用equals来确定一个元素,需要对集合内的元素逐个调用equals方法,效率太低。因此加入了hashcode方法,将元素映射到随机的内存地址上,通过hashcode快速定位到元素(大致)所在的内存地址,再通过使用equals方法确定元素的精确位置。 比较两个元素时,先比较hashcode,如果hashcode不同,则元素一定不相等;如果相同,再用equals判断。
  • HashMap采用这两个方法实现散列存储,提高键的索引性能。HashSet是基于HashMap实现的。

25.HashMap和Hashtable有什么区别

  • HashMap是非线程安全的,HashTable是线程安全的。
  • HashMap的键和值都允许有null值存在,而HashTable则不行。
  • 因为线程安全的问题,HashMap效率比HashTable的要高。
  • Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
  • 一般现在不建议用HashTable,
    • ①是HashTable是遗留类,内部实现很多没优化和冗余。
    • ②即使在多线程环境下,现在也有同步的ConcurrentHashMap替代,没有必要因为是多线程而用HashTable。

26.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?

  • ArrayList可以算是Array的加强版,(对array有所取舍的加强),他的底层还是数组。

  • 存储内容比较:
    Array数组可以包含基本类型和对象类型,
    ArrayList却只能包含对象类型。
    但是需要注意的是:Array数组在存放的时候一定是同种类型的元素。ArrayList就不一定了,因为ArrayList可以存储Object。

  • 空间大小比较:
    它的空间大小是固定的,空间不够时也不能再次申请,所以需要事前确定合适的空间大小。
    ArrayList的空间是动态增长的,如果空间不够,底层数组会以1.5倍进行增长,然后将所有元素复制到新数组中,接着抛弃旧数组。而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。(比较麻烦的地方)。

  • 方法上的比较:
    ArrayList作为Array的增强版,当然是在方法上比Array更多样化,比如添加全部addAll()、删除全部removeAll()、返回迭代器iterator()等。

  • 适用场景:
    如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里,但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择ArrayList。而且还有一个地方是必须知道的,就是如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用ArrayList就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择LinkedList。

27.ArrayList和LinkedList有什么区别?

  • ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
    • ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
    • 相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
    • LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

28.Comparable和Comparator接口是干什么的?列出它们的区别。

  • Comparable & Comparator 都是用来实现集合中元素的比较、排序的。
  • 只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序,所以,如想实现排序,就需要在集合外定义 Comparator 接口的方法或在集合内实现 Comparable 接口的方法。
  • Comparator位于包java.util下,而Comparable位于包 java.lang下 Comparable 是一个对象本身就已经支持自比较所需要实现的接口(如 String、Integer 自己就可以完成比较大小操作,已经实现了Comparable接口) 自定义的类要在加入list容器中后能够排序,可以实现Comparable接口,在用Collections类的sort方法排序时,如果不指定Comparator,那么就以自然顺序排序, 这里的自然顺序就是实现Comparable接口设定的排序方式。
  • 而 Comparator 是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。 可以说一个是自已完成比较,一个是外部程序实现比较的差别而已。
  • 用 Comparator 是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。 比如:你想对整数采用绝对值大小来排序,Integer 是不符合要求的,你不需要去修改 Integer 类(实际上你也不能这么做)去改变它的排序行为,只要使用一个实现了 Comparator 接口的对象来实现控制它的排序就行了。

29.什么是java的优先级队列(Priority Queue)?

  • 优先级队列中的元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索。无论何时调用remove方法,总会获得当前优先级队列中的最小元素,但并不是对所有元素都排序。它是采用了堆(一个可以自我调整的二叉树),执行增加删除操作后,可以让最小元素移动到根。

30.你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?

  • 大O符号表示一个程序运行时所需要的渐进时间复杂度上界。
  • 其函数表示是:
    对于函数f(n),g(n),如果存在一个常数c,使得f(n)<=c*g(n),则f(n)=O(g(n));
    大O描述当数据结构中的元素增加时,算法的规模和性能在最坏情景下有多好。
  • 大O还可以描述其它行为,比如内存消耗。因为集合类实际上是数据结构,因此我们一般使用大O符号基于时间,内存,性能选择最好的实现。大O符号可以对大量数据性能给予一个很好的说明。

31.如何权衡是使用无序的数组还是有序的数组?

数组中存储的数据是有序的还是无序的,以此来区分是有序数组,还是无序数组

        有序数组
                查找,可以使用二分查找, 时间复杂度 O(log N)
                插入,  需要比较,移动数据,找到合适的位置插入数据 O(N)
       
        无序数组:
                查找:需要循环遍历  O(N)
                插入:放到末尾就好 O(1)

32.Java集合类框架的最佳实践有哪些?

这个题问的其实是怎么去选择一个合适的集合类框架

  • java.util.Collection [I]

      |—java.util.List [I]
          |—java.util.ArrayList [C]
          |—java.util.LinkedList [C]
          |—java.util.Vector [C]
              |—java.util.Stack [C]
      |—java.util.Set [I]
          |—java.util.HashSet [C]
          |—java.util.SortedSet [I]
              |—java.util.TreeSet [C]
    
  • java.util.Map [I]

      |—java.util.SortedMap [I]
          |—java.util.TreeMap [C]
      |—java.util.Hashtable [C]
      |—java.util.HashMap [C]
          |—java.util.LinkedHashMap [C]
      |—java.util.WeakHashMap [C]
    

在这里插入图片描述

33.Enumeration接口和Iterator接口的区别有哪些?

枚举速度快,占用内存少,但是不是快速失败的,线程不安全。迭代允许删除底层数据,枚举不行

34.HashSet和TreeSet有什么区别?

HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。
另一方面,TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。

35.Java中垃圾回收有什么目的?什么时候进行垃圾回收?

  • 垃圾回收(gc)的目的是释放堆中不需要保存的对象,达到内存的充分利用。除了释放没用的对象,垃圾回收也可以清除内存里的记录碎片。碎片整理将所占用的堆内存移到堆的一端,以便JVM将整理出的内存分配给新的对象
  • 垃圾回收是在内存中存在没有引用的对象或超过作用域的对象时进行。

36.System.gc()和Runtime.gc()会做什么事情?

  • 这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。
  • java.lang.System.gc()只是java.lang.Runtime.getRuntime().gc()的简写,两者的行为没有任何不同。唯一要能说有什么不同那就是在字节码层面上调用前者比调用后者短一点点,前者是1条字节码而后者是2条

37.finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?

  • 垃圾回收器(garbage collector)决定回收某对象时,就会运行该对象的finalize()方法 但是在Java中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。
  • 那么finalize()究竟是做什么的呢?它最主要的用途是回收特殊渠道申请的内存。Java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalization()的工作就是回收这部分的内存。

38.如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?

不会立即释放对象占用的内存。 如果对象的引用被置为null,只是断开了当前线程栈帧中对该对象的引用关系,而 垃圾收集器是运行在后台的线程,只有当用户线程运行到安全点(safe point)或者安全区域才会扫描对象引用关系,扫描到对象没有被引用则会标记对象,这时候仍然不会立即释放该对象内存,因为有些对象是可恢复的(在 finalize方法中恢复引用 )。只有确定了对象无法恢复引用的时候才会清除对象内存。

39.Java堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space)?

堆内存细分

  • 现代垃圾收集器大部分都基于分代收集理论设计,堆空间细分为:
    • Java 7及之前堆内存逻辑上分为三部分:新生区 + 养老区 + 永久区
      • Young Generation Space 新生区 Young/New
        • 又被划分为 Eden区 和 Survivor区
      • Tenure generation space 养老区 Old/Tenure
      • Permanent Space永久区 Perm
    • Java 8及之后堆内存逻辑上分为三部分:新生区 + 养老区 + 元空间
      • Young/New Generation Space 新生区
        • 又被划分为Eden区和Survivor区
      • Old/Tenure generation space 养老区
      • Meta Space 元空间 Meta
    • 约定:新生区 = 新生代 = 年轻代、 养老区 = 老年区 = 老年代、 永久区 = 永久代

堆空间内部结构(JDK7)
在这里插入图片描述

堆空间内部结构(JDK8)
在这里插入图片描述

年轻代:

  • 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

年老代:

  • 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
    持久代:
  • 用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

40.串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?

想要更深入了解可以查看:JVM与垃圾回收笔记

  • 串行GC:整个扫描和复制过程均采用单线程的方式,相对于吞吐量GC来说简单;适合于单CPU、客户端级别。
  • 吞吐量GC:采用多线程的方式来完成垃圾收集;适合于吞吐量要求较高的场合,比较适合中等和大规模的应用程序。

41.在Java中,对象什么时候可以被垃圾回收?

当一个对象到GC Roots不可达时,在下一个垃圾回收周期中尝试回收该对象,如果该对象重写了finalize()方法,并在这个方法中成功自救(将自身赋予某个引用),那么这个对象不会被回收。但如果这个对象没有重写finalize()方法或者已经执行过这个方法,也自救失败,该对象将会被回收。

42.JVM的永久代中会发生垃圾回收么?

永生代也是可以回收的,条件是 :
1.该类的实例都被回收。
2.加载该类的classLoader已经被回收
3.该类不能通过反射访问到其方法,而且该类的java.lang.class没有被引用
当满足这3个条件时,是可以回收,但回不回收还得看jvm。

43.Java中的两种异常类型是什么?他们有什么区别?

  • Throwable包含了错误(Error)和异常(Excetion两类)
  • Exception又包含了运行时异常(RuntimeException, 又叫非检查异常)和非运行时异常(又叫检查异常)
    (1) Error是程序无法处理了, 如果OutOfMemoryError、StackOverflowError等等, 这些错误发生时, java虚拟机一般会终止线程 .
    (2) 运行时异常都是RuntimeException类及其子类,如 NullPointerException、IndexOutOfBoundsException等, 这些异常是不检查的异常, 是在程序运行的时候可能会发生的, 所以程序可以捕捉, 也可以不捕捉. 这些错误一般是由程序的逻辑错误引起的, 程序应该从逻辑角度去尽量避免.
    (3) 检查异常是运行时异常以外的异常, 也是Exception及其子类, 这些异常从程序的角度来说是必须经过捕捉检查处理的, 否则不能通过编译. 如IOException、SQLException等

44.Java中Exception和Error有什么区别?

  • Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
  • Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
  • Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;ArithmaticException,IllegalArgumentException,编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用try。。。catch捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过。

45.throw和throws有什么区别?

  • Throw用于方法内部,Throws用于方法声明上
  • Throw后跟异常对象,Throws后跟异常类型
  • Throw后只能跟一个异常对象,Throws后可以一次声明多种异常类型

46.异常处理完成以后,Exception对象会发生什么变化?

  • 异常处理对象在异常处理完后,没有引用指向它,变成了不可达对象.
  • 它将在接下来JVM进行gc操作时被标记为"不可达", 如果该Exception实例实现了finalize方法,那么就会安排到F-queue队列中等待执行finalize方法(但是由于F-queue所在线程的优先级很低,所以可能一直得不到执行,而长时间留在该队列中);
  • 再下一次执行gc时,如果Exception对象已经执行完成finalize方法,它将被回收(彻底抹去内存中的数据).

47.finally代码块和finalize()方法有什么区别?

  1. final是关键字,final可以修饰类、方法、属性。
    (1)如果一个类被final修饰,那么这个类就是最终类,不能派生出新的子类,不能作为父类被继承,该类中的所有方法都不能被重写,但是final类中的成员变量是可以改变的,要想final类中的成员变量的不可以改变,必须给成员变量添加final修饰。因此,一个类不能同时被final和abstract修饰,这两个关键字相互矛盾。
    (2) 如果final修饰方法,那么这个方法是最终方法,不允许任何子类重写该方法,但子类仍可以使用该方法,注意:final参数用来表示这个参数在这个函数内部不允许被修改。
    (3)final修饰属性,被final修饰的变量不可变。这里的不可变有两重含义:引用不可变和对象不可变。final指的是引用不可变,即它只能指向初始化时指向的那个对象,而不关心指向对象内容的变化。因此,被final修饰的变量必须初始化,该变量其实就是常量。
  2. finally作为异常处理的一部分,只能用在try/catch语句快中,finally代码块中的语句一定会被执行,经常被用来释放资源,如IO流和数据库资源的释放。
  3. finalize是Object类的一个方法,该方法在Object类中声明:
    protected void finalize() throws Throwable { }
    在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其资源的回收。注意:一旦垃圾回收器准备释放某个对象占用的空间,将首先调该对象的finalize()方法,并且在下一次垃圾回收动作发生时,才真正将该对象占用的内存回收。

48.什么是JDBC?

  • JDBC(Java DataBase Connectivity),是一套面向对象的应用程序接口(API),制定了统一的访问各类关系数据库的标准接口,为各个数据库厂商提供了标准的实现。通过JDBC技术,开发人员可以用纯Java语言和标准的SQL语句编写完整的数据库应用程序,并且真正地实现了软件的跨平台性。
  • 通常情况下使用JDBC完成以下操作:
    1.同数据库建立连接;
    2.向数据库发送SQL语句;
    3.处理从数据库返回的结果;
  • JDBC具有下列优点:
    1.JDBC与ODBC(Open Database Connectivity,即开放数据库互连)十分相似,便于软件开发人员理解;
    2.JDBC使软件开发人员从复杂的驱动程序编写工作中解脱出来,可以完全专注于业务逻辑开发;
    3.JDBC支持多种关系型数据库,大大增加了软件的可移植性;
    4.JDBC API是面向对象的,软件开发人员可以将常用的方法进行二次封装,从而提高代码的重用性;
    在这里插入图片描述

49.解释下驱动(Driver)在JDBC中的角色。

JDBC驱动提供了特定厂商对JDBC API接口类的实现,驱动必须要提供java.sql包下面这些类的实现:Connection, Statement, PreparedStatement,CallableStatement, ResultSet和Driver。
java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。
在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。
在这里插入图片描述

50.Class.forName()方法有什么作用?

  • 初始化参数指定的类,并且返回此类对应的Class 对象
  • 在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字除了一个是方法,一个是关键字外,它们的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。 从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用newInstance()方法的时候,就必须保证:
    1、这个类已经加载;
    2、这个类已经连接了。
  • 而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载 java API的那个加载器。

51.PreparedStatement比Statement有什么优势?

PreparedStatemen采用预编译,减少编译的时间,运行速度更快,PreparedStatement继承与Statement,并封锁的excute等方法的参数,某种程度上的确防止了SQL注入,提高了安全性。

52.数据库连接池是什么意思?

像打开关闭数据库连接这种和数据库的交互可能是很费时的,尤其是当客户端数量增加的时候,会消耗大量的资源,成本是非常高的。可以在应用服务器启动的时候建立很多个数据库连接并维护在一个池中。连接请求由池中的连接提供。在连接使用完毕以后,把连接归还到池中,以用于满足将来更多的请求

53.什么是RMI?

  • Java RMI(Remote Method Invocation)–Java的远程方法调用是Java所特有的分布式计算技术,它允许运行在一个Java虚拟机上的对象调用运行在另一个Java虚拟机上的对象的方法,从而使Java编程人员可以方便地在网络环境中作分布式计算。面向对象设计要求每个任务由最适合该任务的对象执行,RMI将这个概念更深入了一步,使任务可以在最适合该任务的机器上完成。 RMI定义了一组远程接口,可以用于生成远程对象。客户机可以象调用本地对象的方法一样用相同的语法调用远程对象。RMI API提供的类和方法可以处理所有访问远程方法的基础通信和参数引用要求的串行化。 使用RMI开发步骤:
    1、定义一个远程接口(远程接口必须继承接口,每个方法必须抛出远程异常,方法参数和方法返回值都必须是可序列化的)
    2、实现远程接口
    3、定义使用远程对象的客户程序
    4、产生远程访问对象的桩和框
    5、注册远程对象
    6、运行服务器和客户程序

54.RMI体系结构的基本原则是什么?

基本原则就是把对象的使用和对象的创建相分离,我们在服务器端定义类,定义开放哪些方法,并创建对象;在客户端调用对象的方法。

55.RMI体系结构分哪几层?

  • 存根和骨架层(Stub and Skeleton layer):这一层对程序员是透明的,它主要负责拦截客户端发出的方法调用请求,然后把请求重定向给远程的RMI服务。
  • 远程引用层(Remote Reference Layer):RMI体系结构的第二层用来解析客户端对服务端远程对象的引用。这一层解析并管理客户端对服务端远程对象的引用。连接是点到点的。
  • 传输层(Transport layer):这一层负责连接参与服务的两个JVM。这一层是建立在网络上机器间的TCP/IP连接之上的。它提供了基本的连接服务,还有一些防火墙穿透策略。
    在这里插入图片描述

56.RMI中的远程接口(Remote Interface)扮演了什么样的角色?

远程接口用来标识哪些方法是可以被非本地虚拟机调用的接口。远程对象必须要直接或者是间接实现远程接口。实现了远程接口的类应该声明被实现的远程接口,给每一个远程对象定义构造函数,给所有远程接口的方法提供实现。

57.java.rmi.Naming类扮演了什么样的角色?

java.rmi.Naming类用来存储和获取在远程对象注册表里面的远程对象的引用。Naming类的每一个方法接收一个URL格式的String对象作为它的参数。

58.RMI的绑定(Binding)是什么意思?

绑定是为了查询找远程对象而给远程对象关联或者是注册以后会用到的名称的过程。远程对象可以使用Naming类的bind()或者rebind()方法跟名称相关联。

59.Naming类的bind()和rebind()方法有什么区别?

bind()方法负责把指定名称绑定给远程对象,rebind()方法负责把指定名称重新绑定到一个新的远程对象。如果那个名称已经绑定过了,先前的绑定会被替换掉。

60.让RMI程序能正确运行有哪些步骤?

编译所有的源文件。
使用rmic生成stub。
启动rmiregistry。
启动RMI服务器。
运行客户端程序。

61.RMI的stub扮演了什么样的角色?

远程对象的stub扮演了远程对象的代表或者代理的角色。调用者在本地stub上调用方法,它负责在远程对象上执行方法。当stub的方法被调用的时候,会经历以下几个步骤:
初始化到包含了远程对象的JVM的连接。
序列化参数到远程的JVM。
等待方法调用和执行的结果。
反序列化返回的值或者是方法没有执行成功情况下的异常。
把值返回给调用者。

62.什么是分布式垃圾回收(DGC)?它是如何工作的?

DGC叫做分布式垃圾回收。RMI使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。

63.RMI中使用RMI安全管理器(RMISecurityManager)的目的是什么?

RMISecurityManager使用下载好的代码提供可被RMI应用程序使用的安全管理器。如果没有设置安全管理器,RMI的类加载器就不会从远程下载任何的类。

64.解释下Marshalling和demarshalling。

当应用程序希望把内存对象跨网络传递到另一台主机或者是持久化到存储的时候,就必须要把对象在内存里面的表示转化成合适的格式。这个过程就叫做Marshalling,反之就是demarshalling。

65.解释下Serialization和Deserialization。

Java提供了一种叫做对象序列化的机制,他把对象表示成一连串的字节,里面包含了对象的数据,对象的类型信息,对象内部的数据的类型信息等等。因此,序列化可以看成是为了把对象存储在磁盘上或者是从磁盘上读出来并重建对象而把对象扁平化的一种方式。反序列化是把对象从扁平状态转化成活动对象的相反的步骤。

66.什么是Servlet?

Servlet是用来处理客户端请求并产生动态网页内容的Java类。Servlet主要是用来处理或者是存储HTML表单提交的数据,产生动态内容,在无状态的HTTP协议下管理状态信息。

67.说一下Servlet的体系结构。

所有的Servlet都必须要实现的核心的接口是javax.servlet.Servlet。每一个Servlet都必须要直接或者是间接实现这个接口,或者是继承javax.servlet.GenericServlet或者javax.servlet.http.HTTPServlet。最后,Servlet使用多线程可以并行的为多个请求服务。

68.Applet和Servlet有什么区别?

Applet是运行在客户端主机的浏览器上的客户端Java程序。而Servlet是运行在web服务器上的服务端的组件。applet可以使用用户界面类,而Servlet没有用户界面,相反,Servlet是等待客户端的HTTP请求,然后为请求产生响应。

69.GenericServlet和HttpServlet有什么区别?

GenericServlet类实现了Servlet和ServletConfig接口。实现了除了service()之外的其他方法,在创建Servlet对象时,可以继承GenericServlet类来简化程序的代码,但需要实现service()方法。
HttpServlet类继承了GeneriServlet类,为实际开发中大多数用Servlet处理 HTTP请求的应用灵活的方法。

70.解释下Servlet的生命周期。

Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
②装载并创建该Servlet的一个实例对象。
③调用Servlet实例对象的init()方法。
④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。

71.doGet()方法和doPost()方法有什么区别?

doGet:GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。
doPOST:POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。

72.什么是Web应用程序?

Web应用程序是对Web或者是应用服务器的动态扩展。有两种类型的Web应用:面向表现的和面向服务的。面向表现的Web应用程序会产生包含了很多种标记语言和动态内容的交互的web页面作为对请求的响应。而面向服务的Web应用实现了Web服务的端点(endpoint)。一般来说,一个Web应用可以看成是一组安装在服务器URL名称空间的特定子集下面的Servlet的集合。

73.什么是服务端包含(Server Side Include)?

服务端包含(SSI)是一种简单的解释型服务端脚本语言,大多数时候仅用在Web上,用servlet标签嵌入进来。SSI最常用的场景把一个或多个文件包含到Web服务器的一个Web页面中。当浏览器访问Web页面的时候,Web服务器会用对应的servlet产生的文本来替换Web页面中的servlet标签。

74.什么是Servlet链(Servlet Chaining)?

Servlet链是把一个Servlet的输出发送给另一个Servlet的方法。第二个Servlet的输出可以发送给第三个Servlet,依次类推。链条上最后一个Servlet负责把响应发送给客户端。

75.如何知道是哪一个客户端的机器正在请求你的Servlet?

ServletRequest类可以找出客户端机器的IP地址或者是主机名。
getRemoteAddr()方法获取客户端主机的IP地址。
getRemoteHost()可以获取主机名。

76.HTTP响应的结构是怎么样的?

HTTP响应由三个部分组成:

  • 状态码(Status Code):描述了响应的状态。可以用来检查是否成功的完成了请求。请求失败的情况下,状态码可用来找出失败的原因。如果Servlet没有返回状态码,默认会返回成功的状态码HttpServletResponse.SC_OK。
  • HTTP头部(HTTP Header):它们包含了更多关于响应的信息。比如:头部可以指定认为响应过期的过期日期,或者是指定用来给用户安全的传输实体内容的编码格式。如何在Serlet中检索HTTP的头部看这里。
  • 主体(Body):它包含了响应的内容。它可以包含HTML代码,图片,等等。主体是由传输在HTTP消息中紧跟在头部后面的数据字节组成的。

77.什么是cookie?session和cookie有什么区别?

  • cookie是Web服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个Web服务器存储cookie。以后浏览器在给特定的Web服务器发请求的时候,同时会发送所有为该服务器存储的cookie。
  • cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户的状态,就使用response向客户端浏览器颁发一个cookie。客户端浏览器会把cookie保存起来。当浏览器再次请求该网站时,浏览器就会把请求地址和cookie一同给服务器。服务器检查该cookie,从而判断用户的状态。服务器还可以根据需要修改cookie的内容。 session是另一种记录客户状态的机制。不同的是cookie保存在客户端浏览器中,而session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是session。客户端浏览器再次访问时只需要从该session中查找该客户的状态就可以了。 如果说cookie机制是通过检查客户身上的“通信证”,那么session机制就是通过检查服务器上的“客户明细表”来确认客户身份。
  • 下面列出了session和cookie的区别:
    • 无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用cookie,但是,session仍然是能够工作的,因为客户端无法禁用服务端的session。
    • 在存储的数据量方面session和cookies也是不一样的。session能够存储任意的Java对象,cookie只能存储String类型的对象。

78.浏览器和Servlet通信使用的是什么协议?

浏览器和Servlet通信使用的是HTTP协议。

79.什么是HTTP隧道?

HTTP隧道是一种利用HTTP或者是HTTPS把多种网络协议封装起来进行通信的技术。因此,HTTP协议扮演了一个打通用于通信的网络协议的管道的包装器的角色。把其他协议的请求掩盖成HTTP的请求就是HTTP隧道。

80.sendRedirect()和forward()方法有什么区别?

sendRedirect()方法会创建一个新的请求,而forward()方法只是把请求转发到一个新的目标上。
重定向(redirect)以后,之前请求作用域范围以内的对象就失效了,因为会产生一个新的请求,而转发(forwarding)以后,之前请求作用域范围以内的对象还是能访问的。一般认为sendRedirect()比forward()要慢。

81.什么是URL编码和URL解码?

URL编码是负责把URL里面的空格和其他的特殊字符替换成对应的十六进制表示,反之就是解码。

82.什么是JSP页面?

jsp是java开发的专门用于动态显示页面的技术。jsp编译时,会首先编译成servlet文件,然后编译成class文件。
包含9大内置对象:response、request、exception、out、application、pagecontext、config、session、page。
有四个域对象:request、pagecontext、session、application

83.JSP请求是如何被处理的?

浏览器首先要请求一个以.jsp扩展名结尾的页面,发起JSP请求,然后,Web服务器读取这个请求,使用JSP编译器把JSP页面转化成一个Servlet类。需要注意的是,只有当第一次请求页面或者是JSP文件发生改变的时候JSP文件才会被编译,然后服务器调用servlet类,处理浏览器的请求。一旦请求执行结束,servlet会把响应发送给客户端。

84.JSP有什么优点?

JSP页面是被动态编译成Servlet的,因此,开发者可以很容易的更新展现代码。
JSP页面可以被预编译。
JSP页面可以很容易的和静态模板结合,包括:HTML或者XML,也可以很容易的和产生动态内容的代码结合起来。
开发者可以提供让页面设计者以类XML格式来访问的自定义的JSP标签库。
开发者可以在组件层做逻辑上的改变,而不需要编辑单独使用了应用层逻辑的页面。

85.什么是JSP指令(Directive)?JSP中有哪些不同类型的指令?

  • Directive是当JSP页面被编译成Servlet的时候,JSP引擎要处理的指令。Directive用来设置页面级别的指令,从外部文件插入数据,指定自定义的标签库。Directive是定义在 <%@ 和 %>之间的。
  • 下面列出了不同类型的Directive:
    包含指令(Include directive):用来包含文件和合并文件内容到当前的页面。
    页面指令(Page directive):用来定义JSP页面中特定的属性,比如错误页面和缓冲区。
    Taglib指令: 用来声明页面中使用的自定义的标签库。

86.什么是JSP动作(JSP action)?

JSP动作以XML语法的结构来控制Servlet引擎的行为。当JSP页面被请求的时候,JSP动作会被执行。它们可以被动态的插入到文件中,重用JavaBean组件,转发用户到其他的页面,或者是给Java插件产生HTML代码。下面列出了可用的动作:
jsp:include-当JSP页面被请求的时候包含一个文件。
jsp:useBean-找出或者是初始化Javabean。
jsp:setProperty-设置JavaBean的属性。
jsp:getProperty-获取JavaBean的属性。
jsp:forward-把请求转发到新的页面。
jsp:plugin-产生特定浏览器的代码。

87.什么是Scriptlets?

指jsp页面里 <% %> 中间的代码。
JSP技术中,scriptlet是嵌入在JSP页面中的一段Java代码。scriptlet是位于标签内部的所有的东西,在标签与标签之间,用户可以添加任意有效的scriplet。

88.声明(Decalaration)在哪里?

声明跟Java中的变量声明很相似,它用来声明随后要被表达式或者scriptlet使用的变量。添加的声明必须要用开始和结束标签包起来。

89.什么是表达式(Expression)?

【列表很长,可以分上、中、下发布】
JSP表达式是Web服务器把脚本语言表达式的值转化成一个String对象,插入到返回给客户端的数据流中。表达式是在<%=和%>这两个标签之间定义的。

90.隐含对象是什么意思?有哪些隐含对象?

SP隐含对象是页面中的一些Java对象,JSP容器让这些Java对象可以为开发者所使用。开发者不用明确的声明就可以直接使用他们。JSP隐含对象也叫做预定义变量。下面列出了JSP页面中的隐含对象:
application
page
request
response
session
exception
out
config
pageContext

91.面向对象软件开发的优点有哪些?

代码开发模块化,更易维护和修改。
代码复用。
增强代码的可靠性和灵活性。
增加代码的可理解性。
面向对象编程有很多重要的特性,比如:封装,继承,多态和抽象。

92.封装的定义和好处有哪些?

封装给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法来改变它内部的数据。在Java当中,有3种修饰符:public,private和protected。每一种修饰符给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。
下面列出了使用封装的一些好处:
通过隐藏对象的属性来保护对象内部的状态。
提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展。
禁止对象之间的不良交互提高模块化。

93.多态的定义?

父类的引用指向子类的实例
多态:相同类型的引用变量,调用同一个方法时呈现出多种不同的行为特征。
对象的实例变量不具备多态性。
Java引用变量有两个类型:编译时类型,运行时类型
编译时类型由声明该变量时使用的类型决定(编译看父类)
运行时类型由实际赋给该变量的对象决定(运行看子类)
如果编译时类型和运行时类型不一致,就可能出现多态(Polymorphism)

94.继承的定义?

继承给对象提供了从基类获取字段和方法的能力。继承提供了代码的重用性,也可以在不修改类的情况下给现存的类添加新特性。

95.抽象的定义?抽象和封装的不同点?

抽象是把想法从具体的实例中分离出来的步骤,因此,要根据他们的功能而不是实现细节来创建类。Java支持创建只暴漏接口而不包含方法实现的抽象的类。这种抽象技术的主要目的是把类的行为和实现细节分离开。
抽象和封装是互补的概念。一方面,抽象关注对象的行为。另一方面,封装关注对象行为的细节。一般是通过隐藏对象内部状态信息做到封装,因此,封装可以看成是用来提供抽象的一种策略。

参考

牛客网

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Listen·Rain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值