Java常见知识
一.Java基础
1) String StringBuffer StringBuilder区别
- String:不可变字符串; StringBuffer:可变字符串、效率低、线程安全;StringBuilder:可变字符序列、效率高、线程不安全;
- String可以空赋值,其他不行
- 如果要操作少量的数据用 String;多线程操作字符串缓冲区下操作大量数据 StringBuffer;单线程操作字符串缓冲区下操作大量数据 StringBuilder(推荐使用)。
2) 集合List、Set、Map
1. 他们的底层实现?
-
- ArrayList: 底层数组,查询快,增删慢,线程不安全,效率高。
- LinkedList: 底层链表,增删快,查询慢,线程不安全,效率高。
- Vector: 底层数据结构是数组,查询快,增删慢,线程安全,效率低。
-
- HashSet: 底层hash表,通过hashCode和equals方法保证元素唯一。
- LinkedHashSet: 底层hash表和链表,由链表保证元素顺序,由hash表保证元素唯一。
- TreeSet: 底层数据结构是红黑树,按序存放,想要有序就要实现Comparable接口。
-
- HashMap: 底层数据结构是哈希表。线程不安全,效率高。哈希码算法,快速查找键值。
- LinkedHashMap: 底层数据结构由链表和哈希表组成,由链表保证元素有序,由哈希表保证元素唯一。
- TreeMap: 底层数据结构是红黑树,对键按序存放。
2. List和Set的区别?
- list可允许重复对象,set不允许。
- list可插入多个null元素,set只能有一个。
- list是有序的,输出顺序就是插入顺序,set是无序的,无法保证存储顺序,但是TreeSet通过Comparator或者Comparable维护了一个排序顺序。
- 底层数据结构不同。
3. ArrayList和Vector的区别?
- ArrayList线程不安全,效率高,Vector加了很多同步锁,线程安全效率低。
- ArrayList和Vector都采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加为原来的50%,Vector默认增加为原来的一倍。
- Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。
4. ArrayList,Vector, LinkedList的存储性能和特性?
- ArrayList 采用的是数组形式来保存对象的,这种方式将对象放在连续的位置中,所以最大的缺点就是插入删除时非常麻烦。
- LinkedList 采用的将对象存放在独立的空间中,而且在每个空间中还保存下一个链接的索引 但是缺点就是查找非常麻烦 要丛第一个索引开始。
- ArrayList和Vector都是用数组方式存储数据,此数组元素数要大于实际的存储空间以便进行元素增加和插入操作,他们都允许直接用序号索引元素,但是插入数据元素涉及到元素移动等内存操作,所以索引数据快而插入数据慢。
- Vector使用了sychronized方法(线程安全),所以在性能上比ArrayList要差些。
- LinkedList使用双向链表方式存储数据,按序号索引数据需要前向或后向遍历数据,所以索引数据慢,是插入数据时只需要记录前后项即可,所以插入的速度快。
5. Conllection和Collections的区别?
- Collection是集合类的上级接口,继承与他有关的接口主要有List和Set。
- Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作。
6. Map的底层,扩展因子是多少,如何解决hash碰撞?
0.75 一般采用拉链法,将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
7. 如何控制Map集合是线程安全的?
用建议使用ConcurrentHashMap。ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。
3) 接口和抽象类的区别
- 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法
- 接口中的实例变量默认是 final 类型的,而抽象类中则不一定
- 一个类可以实现多个接口,但最多只能实现一个抽象类
- 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
- 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
4) 什么是方法重载,什么是方法重写,构造方法可以重写或者重载吗?
- 重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
- 重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;
如果父类方法访问修饰符为 private 则子类就不能重写该方法。
构造方法不能重写,可以重载。
二. Java多线程
1) Java中实现线程的方式?
Runable Thread Callable
- 继承Thread类,Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
- 实现Runable接口,重写run方法。如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口。
- 实现Callable接口,重写call方法。
2) Callable实现的多线程和Thread的线程有什么区别?
1. 相同点:
- 两者都是接口;两者都可用来编写多线程程序;两者都需要调用Thread.start()启动线程;
2. 不同点:
- Callable规定的方法是call(),Runnable规定的方法是run()。
- 实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果。
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。
- 运行Callable任务可以拿到一个Future对象,Future 表示异步计算的结果。
3) 如何保证线程同步?
- 同步方法,即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
- 同步代码块,即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
- 使用特殊域变量(volatile)实现线程同步
- volatile关键字为域变量的访问提供了一种免锁机制,
- 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
- 因此每次使用该域就要重新计算,而不是使用寄存器中的值 ,d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
- 使用重入锁实现线程同步,在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
- 使用局部变量实现线程同步 。如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。synchronized 和 Lock
4) ThreadLocal和volatile的理解
- ThreadLocal是用于解决多线程共享类的成员变量,原理:在每个线程中都存有一个本地ThreadMap,相当于存了一个对象的副本,key为threadlocal对象本身,value为需要存储的对象值,这样各个线程之间对于某个成员变量都有自己的副本,不会冲突。
- volatile保证了成员变量在多线程下的可见性和有序性,不保证原子性,java中原子性的操作为读取一个值或者给一个变量赋值。
- 线程在执行完一行代码,例如修改了一个变量的值,因为工作内存速度小于主存中高速CPU的速度,所以对于其他的线程而言,读取到该变量的值可能不是最新的值。
- 使用volatile关键字的时候,该变量一旦被修改,会立即写入到主存中,同时会让其他线程的工作内存中的缓存失效,这样,其他线程在访问该变量的时候会重新从主存中读取可以获得该变量最新的数据,从而保证的变量的可见性。
5) Sleep()、wait()之间有什么区别?
- sleep() 方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。 因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
- wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。
6) 如何保证线程的有序执行?
- join方法。
- 利用并发包里的Excutors的newSingleThreadExecutor产生一个单线程的线程池。
7) Java中活锁和死锁有什么区别?
- 死锁:两个或多个线程相互等待对方释放锁,则会出现死锁现象。java虚拟机没有检测,也没有采用措施来处理死锁情况,所以多线程编程是应该采取措施避免死锁的出现。一旦出现死锁,整个程序即不会发生任何异常,也不会给出任何提示,只是所有线程都处于堵塞状态。
- 任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
- 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
8) 如何避免死锁?
- 加锁顺序:当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。这种方式是一种有效的死锁预防机制。但是,这种方式需要你事先知道所有可能会用到的锁,但总有些时候是无法预知的。
- 加锁时限:在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁。
- 死锁检测:死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。每当一个线程获得了锁,会在线程和锁相关的数据结构中(map等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。检测到死锁之后:一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁。
9) 什么是可重入锁(ReentrantLock)?
- 可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁
10) 线程start()方法和run()方法简介和区别?
- start方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
- run方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
11) 什么是乐观锁和悲观锁?
- 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block(阻塞)直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
- 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
12) 你了解哪些线程并发编程同步类?
- CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
- CyclicBarrier字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。CyclicBarrier 和 CountDownLatch
13) 如何在两个线程间共享数据?
利用wait和notify实现数据的同步
14) 如何在Java中创建线程安全的Singleton
- 饿汉模式创建单例对象
- 加synchronized锁的懒汉模式
- 静态代码块
- 静态内部类
- 双锁:声明变量加volatile锁,获取对象加synchronized锁
15) 线程池的实现,使用JDK提供的Executor线程池框架
理解newFixedThreadPool的参数含义https://blog.csdn.net/sx1119183530/article/details/79734953
三.Java虚拟机
1) java内存模型
- Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。
- Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面将的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成。
2) java类初始化流程
- JVM会先去方法区中找有没有相应类的.class存在。如果有,就直接使用;如果没有,则把相关类的.class加载到方法区
- 在.class加载到方法区时,会分为两部分加载:先加载非静态内容,再加载静态内容
- 加载非静态内容:把.class中的所有非静态内容加载到方法区下的非静态区域内
- 加载静态内容:
- 把.class中的所有静态内容加载到方法区下的静态区域内
- 静态内容加载完成之后,对所有的静态变量进行默认初始化
- 所有的静态变量默认初始化完成之后,再进行显式初始化
- 当静态区域下的所有静态变量显式初始化完后,执行静态代码块
- 当静态区域下的静态代码块,执行完之后,整个类的加载就完成了。
- 如果存在继承关系,则父类先加载,再加载子类。
3) java的类加载机制
Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能。虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
4) java垃圾回收机制和垃圾回收算法
JVM分别对新生代和旧生代采用不同的垃圾回收机制,新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到旧生代。旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。
常见算法:
1.Mark-Sweep(标记-清除)算法:
这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
缺点:容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作
2.Copying(复制)算法:为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
优点:实现简单,运行高效且不容易产生内存碎片。
缺点:对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。如果存活对象很多,那么Copying算法的效率将会大大降低。
3.Mark-Compact(标记-整理)算法:
该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
4.Generational Collection(分代收集)算法:
对新生代采取copying算法,新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
5) java如何手动通知JVM进行垃圾回收,回立即执行吗?
system.gc()方法,不能立即执行。JVM也不会立即启动垃圾回收机制的,回收机制只有JVM自己去启动。
6) java堆栈的调整参数?
四.Java设计模式
1) 单例模式? 饿汉式和饱汉式
2) 装饰者模式
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
3) 工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
五. 排序算法
1) 冒泡和快速
六.Mysql数据库
1) mysql的数据库存储引擎有哪些?
- InnoDB存储引擎,
- MyISAM存储引擎,
- MEMORY存储引擎,
- Archive存储引擎。
MyISAM和InnoDB的区别:
- InnoDB:
- 支持事务处理等
- 不加锁读取
- 支持外键
- 支持行锁
- 不支持FULLTEXT类型的索引
- 不保存表的具体行数,扫描表来计算有多少行
- DELETE 表时,是一行一行的删除
- InnoDB 把数据和索引存放在表空间里面
- 跨平台可直接拷贝使用
- InnoDB中必须包含AUTO_INCREMENT类型字段的索引
- 表格很难被压缩
- MyISAM:
- 不支持事务,回滚将造成不完全回滚,不具有原子性
- 不支持外键
- 支持全文搜索
- 保存表的具体行数,不带where时,直接返回保存的行数
- DELETE 表时,先drop表,然后重建表
- MyISAM 表被存放在三个文件,frm 文件存放表格定义,数据文件是MYD (MYData),索引文件是MYI (MYIndex)。引伸
- 跨平台很难直接拷贝
- MyISAM中可以使AUTO_INCREMENT类型字段建立联合索引
- 表格可以被压缩
MyISAM和InnoDB的区别 https://www.cnblogs.com/szlbm/p/5513031.html
2) mysql索引的数据结构
3) mysql数据库层面的优化 : https://www.toutiao.com/i6564144479245173255/
4) sql语句的优化:https://www.cnblogs.com/Little-Li/p/8031295.html
5) truncate和delete的区别
- TRUNCATE在各种表上无论是大的还是小的都非常快。如果有ROLLBACK命令DELETE将被撤销,而TRUNCATE则不会被撤销。
- truncate不能进行回滚操作。
- truncate不触发任何delete触发器。
- 当表被truncate后,这个表和索引所占用的空间会恢复到初始大小,而delete操作不会减少表或索引所占用的空间。
- 不能truncate一个带有外键的表,如果要删除首先要取消外键,然后再删除。
- DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的的删除操作作为事务记录在日志中保存以便进行进行回滚操作。
6) mysql数据库的悲观锁? for update
七. Java Web
1) cookie和session的区别
- Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一服务器,是在客户端保持状态的方案。
- 存在服务器的一种用来存放用户数据的类HashTable结构。区别:存储数据量方面:session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象一个在客户端一个在服务端。因Cookie在客户端所以可以编辑伪造,不是十分安全。Session过多时会消耗服务器资源,大型网站会有专门Session服务器,Cookie存在客户端没问题。域的支持范围不一样,比方说a.com的Cookie在a.com下都能用,而www.a.com的Session在api.a.com下都不能用,解决这个问题的办法是JSONP或者跨域资源共享。
2) http协议简单协议
3) 网络七层协议
OSI的7层从上到下分别是 7 应用层 6 表示层 5 会话层 4 传输层 3 网络层 2 数据链路层 1 物理层 。其中高层(即7、6、5、4层)定义了应用程序的功能,下面3层(即3、2、1层)主要面向通过网络的端到端的数据流。
分层优点:
- 人们可以很容易的讨论和学习协议的规范细节。
- 层间的标准接口方便了工程模块化。
- 创建了一个更好的互连环境。
- 降低了复杂度,使程序更容易修改,产品开发的速度更快。
- 每层利用紧邻的下层服务,更容易记住各层的功能。
4) jsp九大内置对象
- request
- respose
- session
- page
- pagecontext
- application
- out
- exception
- config
5) jsp四大作用域
- application
- pagecontext
- cookie
- session
6) servlet的生命周期
servlet有3个生命周期函数:
- init方法:只会执行一次,一般在服务器启动第一个用户请求的时候调用,也可以配置成服务器启动的时候自动执行。
- init方法负责简单的创建或者加载一些数据,这些数据将用于该Servlet的整个生命周期中。
- service方法:当一个客户请求该Servlet时,实际的处理工作全部有service方法来完成,service方法用来处理客户端的请求,并生成格式化数据返回给客户端。
- 每一次请求服务器都会开启一个新的线程并执行一次service方法,service根据客户端的请求类型,调用doGet、doPost等方法。
- destroy方法:该方法在整个生命周期中,也是只会被调用一次,在Servlet对象被销毁是调用,在servlet中,我们可以做一些资源的释放等操作,
- 执行destory方法之后的servlet对象,会等待jvm虚拟机的垃圾回收机制择时回收。
7) jsp和servlet的区别
- jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)
- jsp更擅长表现于页面显示,servlet更擅长于逻辑控制.
- Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到.
- Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件
8) filter的生命周期
- init方法:当web容器启动的时候,就会自动调用init(FilterConfig arg0)来对filter进行初始化。
- dofilter方法:filter核心方法,执行监听的核心方法。
- destory方法:当关闭web容器,关机,或者reload整个应用时,都会调用destroy()来关闭filter。