java面试

作者: 4839068c3f32 | 来源:发表于2017-12-21 19:50 被阅读40次

    https://3262674949.docs.qq.com/WELCOMEDOC?type=1&_wv=1&domainId=26810955&listver=2&opendocxfrom=tim&ADUIN=506551682&ADSESSION=1513729865&ADTAG=CLIENT.QQ.5537_.0&ADPUBNO=26752

    java知识点整理笔记

    1.==和equals的区别

    答:对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;如果作用于引用类型的变量,则比较的是所指向的对象的地址。对于equals方法,注意:equals方法不能作用于基本数据类型的变量,如果没有对equals方法进行重写,则比较的是引用类型的变量所指向对象的内存地址;如果对equals方法进行了重写的话,比较的是所指向的对象的内容,例如String类。

    2.形参和实参的区别

    答:形参

    定义:全称"形式参数",用于定义方法的时候使用的参数,目的用来接收调用该方法时传递的参数。

    说明:只有在被调用时才会分配内存单元,在调用结束,即刻释放所分配的内存单元。因此,只在方法内才有效

    实参

    定义:全称"实际参数",用于调用时传递给方法的参数,即传递给被调用方法的值。

    说明:预先创建并赋予确定值。

    1、形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。

    因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量。

    2、实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,

    它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值。

    3、实参和形参在数量上,类型上、顺序上应严格一致,否则就会发生类型不匹配的错误。

    4、在一般传值调用的机制中只能把实参传送给形参,而不能把形参的值反向地传送给实参。

    因此在函数调用过程中,形参值发生改变,而实参中的值不会变化。

    而在引用调用的机制当中是将实参引用的地址传递给了形参,所以任何发生在形参上的改变实际上也发生在实参变量上。

    5.  值传递:方法调用时,实参把他对应的值传递给对应的形参,方法中执行形参的改变而不影响实参

    引用传递: 传的是地址值,方法调用时 实参的引用被传递给方法中对应的形参 在方法中执行对形参的操作就是对实参的操作

    3.final的特点

    答:对于基本类型,final使数值恒定不变;而对用对象引用,final使引用恒定不变。

    final修饰的基本类型,一旦被初始化后,不能再被赋值。

    final修饰的对象引用,一旦引用被初始化指向一个对象,就无法再把它改为指向另外一个对象。

    当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。

    final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

    使用final修饰方法的参数,若参数为基本类型,该参数不能在方法中修改其值。若参数为对象应用,该参数在方法中不能修改其指向引用。

    4. final、finally、finallize的区别

    答:final

    当这个关键字修饰一个类时,意味着他不能派生出新的子类,也就是说不能被继承,因此一个类不能被同时声明为abstract和final。当final修饰变量或者方法时,可以保证他们在使用中不会被改变。被声明为final的变量必须在初始化时给定初值,以后在使用时只能被引用而不能被修改。同样,当final修饰一个方法时,这个方法不能被重载。

    finally

    异常处理时提供finally来执行任何清楚操作。如果抛出一个异常,那么相匹配的catch子句就会被执行,然后控制就会转入finally块。

    finalize

    方法名。finalize方法在垃圾回收器执行内存对象清理时会调用finalize()方法进行前期的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。

    Java中所有类都从Object类中继承finalize()方法。

    当垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法。

    5.android有哪些新集合,有什么特点

    答:

    - ArrayMap

    它不是一个适应大数据的数据结构,相比传统的HashMap速度要慢,

    因为查找方法是二分法,并且当你删除或者添加数据时,会对空间重新调整,

    在使用大量数据时,效率并不明显,低于50%。

    所以ArrayMap是牺牲了时间换区空间。在写手机app时,适时的使用ArrayMap,会给内存使用带来可观的提升。

    - ArraySet

    代替hashset

    - SparseArray

    1,SparseArray的原理是二分检索法,也因此key的类型都是整型。

    2,(HashMap和SparseArray比较)当存储大量数据(起码上千个)的时候,优先选择HashMap。

    如果只有几百个,用哪个区别不大。如果数量不多,优先选择SparseArray。

    3,SparseArray有自己的垃圾回收机制。(当数量不是很多的时候,这个不必关心。)

    SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间,

    满足下面两个条件我们可以使用SparseArray代替HashMap:

    数据量不大,最好在千级以内

    key必须为int类型,

    - SparseBooleanArray

    - SparseIntArray

    SparseBooleanArray和SparseIntArray,其实看名字也知道,它们跟SparseArray极其类似,只是存储类型加以限制了。SparseBooleanArray只能存储boolean值,而SparseIntArray只能存储integer类型的值。它们也同样实现了Cloneable接口,可以直接调用clone方法,也同样是以二分法为依据。

    - SparseLongArray

    6.新IO是什么,有什么特点

    答:java.nio全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,

    为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络

    旧IO:

    JDK1.4以前,I/O输入输出处理,称为旧IO处理

    新IO:

    JDK1.4开始,JAVA提供了一系列改进的输入输出新特性,称为新I/O

    新添了许多用于处理输入输出的类,放在java.nio包及子包下。

    并且对原java.io包中的很多类以NIO为基础进行了改写。新添了满足新I/O的功能。

    特点:

    新 IO采用内存映射文件来处理输入输出。

    新 IO将文件或文件的一部分映射到内存中,就可以像访问内存一样来访问文件了。所以这种方式既方便也快捷。

    既可以从通道中读取数据,又可以写数据到通道中。流的读写通常是单向的。

    通道可以异步读写。

    通道中的数据总是先读到一个Buffer中,或者总是从一个Buffer中写入。

    7.TreeSet是如何进行排序的?

    答: 自然排序:①要求添加进TreeSet中的元素所在的类implements Comparable接口

    ②重写compareTo(Object obj),在此方法内指明按照元素的哪个属性进行排序

    ③向TreeSet中添加元素即可。若不实现此接口,会报运行时异常

    比较器排序:①创建一个实现Comparator接口的实现类的对象。在实现类重 写Comparator的compare(Object o1,Object o2)方法

    ②在此compare()方法中指明按照元素所在类的哪个属性进行排序

    ③将此实现Comparator接口的实现类的对象作为形参传递给TreeSet的构造器中

    ④向TreeSet中添加元素即可。若不实现此接口,会报运行时异常

    要求重写的compareTo()或者compare()方法与equals()和hashCode()方法保持一致。

    8.  什么是线程 ?

    答:    线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

    9.线程都有哪些状态?

    答:1.新建状态(New):

    当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码

    2.就绪状态(Runnable)

    一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

    处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

    3.运行状态(Running)

    当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.

    4. 阻塞状态(Blocked)

    线程运行过程中,可能由于各种原因进入阻塞状态:

    1>线程通过调用sleep方法进入睡眠状态;

    2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;

    3>线程试图得到一个锁,而该锁正被其他线程持有;

    4>线程在等待某个触发条件;

    所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

    5. 死亡状态(Dead)

    有两个原因会导致线程死亡:

    1) run方法正常退出而自然死亡,

    2) 一个未捕获的异常终止了run方法而使线程猝死。

    为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

    10.什么是线程池?

    答:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

    线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。

    如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

    11.线程和进程的区别

    答:进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

    1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

    2) 线程的划分尺度小于进程,使得多线程程序的并发性高。

    3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

    4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

    5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

    12.如何在Java中实现线程?

    答:

    一、继承Thread类创建线程类

    (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。

    (2)创建Thread子类的实例,即创建了线程对象。

    (3)调用线程对象的start()方法来启动该线程。

    二、通过Runnable接口创建线程类

    (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

    (2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

    (3)调用线程对象的start()方法来启动该线程。

    三、通过Callable和Future创建线程

    (1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

    (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

    (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

    (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值,调用get()方法会阻塞线程。

    13.start方法和run方法的区别

    答:1.start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。

    然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。

    2.run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。

    14.线程池的最大线程数是多少?

    答:不同的任务类别应采用不同规模的线程池,任务类别可划分为CPU密集型任务、IO密集型任务和混合型任 务。(N代表CPU个数)

    对于CPU密集型任务:线程池中线程个数应尽量少,如配置N+1个线程的线程池;

    对于IO密集型任务:由于IO操作速度远低于CPU速度,那么在运行这类任务时,CPU绝大多数时间处于空闲状态,那么线程池可以配置尽量多些的线程,以提高CPU利用率,如2*N;

    对于混合型任务:可以拆分为CPU密集型任务和IO密集型任务,当这两类任务执行时间相差无几时,通过拆分再执行的吞吐率高于串行执行的吞吐率,但若这两类任务执行时间有数据级的差距,那么没有拆分的意义。

    15.HandlerThread是什么?

    答:    HandlerThread = Handler + Thread + Looper

    HandlerThread是一个内部有Looper的Thread。

    1)HandlerThread本质上是一个线程类,它继承了Thread。

    2)HandlerThread有自己的内部Looper对象,可以进行looper循环。

    3)通过获取HandlerThread的Looper对象传递给Handler对象,可以在handleMessae方法中执行异步任务。

    4)优点是不会有堵塞,减少了对性能的消耗。

    5)缺点是不能同时进行多任务处理,需要进行等待,处理效率较低。

    6)与线程池侧重并发不同,HandlerThread是一个串行队列,HandlerThread背后只有一个线程。

    16. HandlerThread 的原理

    答:HandlerThread在run()方法中调用Looper.myLooper()创建了一个Looper对象mLooper,

    并将mLooper放到线程变量sThreadLocal中,然后通过Looper.looper()开启消息循环,

    Looper.looper()方法会不断从MessageQueue中取出消息处理消息,没有消息则会阻塞。

    getLooper()方法是获取线程中的Looper对象,用来初始化Handler对象。

    quit()             会清空所有消息(无论延时或是非延时)

    quitSafely()   只会清空延时消息

    Looper             无论是调用了quit()方法还是quitSafely()方法,Looper就不再接收新的消息,

    即在调用了quit()方法或quitSafely()方法之后,消息循环也就终结了,

    这时候再通过sendMessage或post等方法发送消息时均返回false,线程结束。

    HandlerThread继承自Thread,因此在run()中的逻辑都是在子线程中运行的。

    接下来就是两个关键的方法,run()和getLooper():

    run()中可以看到是很简单的创建Looper以及让Looper工作的逻辑。

    run()里面当mLooper创建完成后有个notifyAll(),getLooper()中有个wait(),这有什么用呢?因为的mLooper在一个线程中执行创建,而我们的handler是在UI线程中调用getLooper()初始化的。

    也就是说,我们必须等到mLooper创建完成,才能正确的返回。

    getLooper();wait(),notify()就是为了解决这两个线程的同步问题。

    17.用 Runnable 还是 Thread ?

    答:1):适合多个相同的程序代码的线程去处理同一个资源

    2):可以避免java中的单继承的限制

    3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

    18.Runnable 和 Callable 有什么不同?

    答:Callable规定的方法是call(),                而Runnable规定的方法是run()。

    Callable的任务执行后可返回值,        而Runnable的任务是不能返回值。

    call()方法可抛出异常                        而run()方法是不能抛出异常的。

    运行Callable任务可拿到一个Future对象,Future表示异步计算结果。

    它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算结果。

    通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。

    Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。

    19.使用多线程的优缺点

    答:使用多线程可以减少程序的响应时间,如果某个操作很耗时,或者陷入长时间的等待,此时程序将不会响应鼠标和键盘等的操作,使用多线程后就可以把这个耗时的操作分配到一个单独的线程中去运行,从而使得程序具备了良好的交互性与进程相比,多线程的开创和切换开销更小,同时多线程在数据的共享方面效率非常高使用多线程能简化程序的的结构,使得程序便于理解和维护

    20.wait()、notify()、notifyAll()的区别

    答:wait

    Object的wait方法有三个重载方法,其中一个方法wait() 是无限期(一直)等待,直到其它线程调用notify或notifyAll方法唤醒当前的线程;另外两个方法wait(long timeout) 和wait(long timeout, int nanos)允许传入当前线程在被唤醒之前需要等待的时间,timeout为毫秒数,nanos为纳秒数。

    notify

    notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。

    notifyAll

    notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。

    21.wait() 与 Thread.sleep(long time) 的区别

    答:wait()

    wait来自Object类,wait方法释放锁,wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。

    一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,

    要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。

    Thread.sleep(long tiem)

    sleep来自Thread类,sleep方法没有释放锁,sleep不出让系统资源。

    sleep(milliseconds)可以用时间指定使它自动唤醒过来,

    如果时间不到只能调用interrupt()强行打断。

    22. volatile关键字的作用

    答:(1)保证了这个变量对所有线程的可见性。

    一个共享变量被volatile修饰之后,假设有多个线程用到了这个变量。

    如果一个线程中对这个变量的值做了修改。

    那么

    第一步:该线程中修改后的值立即刷新同步主内存中对应的值;

    第二步:通知其他线程,他们的工作内存中原来缓存的该变量已过期无效,需重新从主内存中读取。

    (2)禁止进行指令重排序优化,即保证了有序性。

    ①当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,

    且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

    ②在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,

    也不能把volatile变量后面的语句放到其前面执行。

    23.join方法的作用

    答:join方法的原理就是调用相应线程的wait方法进行等待操作的,例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。

    24.yield方法的作用

    答:Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)

    yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。

    因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。

    但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

    25.线程池的优点

    答:1.减少在创建和销毁线程上所花的时间以及系统资源的开销

    2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存

    重用线程池中的线程,减少因对象创建,销毁所带来的性能开销;

    能有效的控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;

    能够多线程进行简单的管理,使线程的使用简单、高效。

    26.有几种线程池,分别是什么?

    答:1、newFixedThreadPool 创建一个指定工作线程数量的线程池。

    每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

    2、newCachedThreadPool 创建一个可缓存的线程池。

    这种类型的线程池特点是:

    1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。

    2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

    3、newSingleThreadExecutor 创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。

    单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的 。

    4、newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

    27.如何关闭线程池?

    答:shutdown关闭线程池

    方法定义:public void shutdown()

    (1)线程池的状态变成SHUTDOWN状态,此时不能再往线程池中添加新的任务,否则会抛出RejectedExecutionException异常。

    (2)线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

    (3)试图停止所有正在执行的线程,试图终止的方法是调用Thread.interrupt(),但是大家知道,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,shutdown()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

    shutdownNow关闭线程池并中断任务

    方法定义:public List shutdownNow()

    (1)线程池的状态立刻变成STOP状态,此时不能再往线程池中添加新的任务。

    (2)终止等待执行的线程,并返回它们的列表;

    注意这个函数不会等待提交的任务执行完成,要想等待全部任务完成,可以调用:

    public boolean awaitTermination(longtimeout, TimeUnit unit)

    28.如何关闭线程?

    答:1. 当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

    2、 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。

    3、  使用interrupt方法中断线程。

    4、        抛出异常

    29.什么是 Executor 框架?

    答:Executor框架同java.util.concurrent.Executor 接口在Java 5中被引入。Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。

    无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用Executor框架可以非常方便的创建一个线程池。

    30.Executors 类是什么?

    答: Executor接口是Executor框架中最基础的部分,定义了一个用于执行Runnable的execute方法。它没有直接的实现类,有一个重要的子接口ExecutorService。

    Executors

    Executors中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。

    31. Handler原理|如何进行线程间通信?

    答:当创建Handler时将通过ThreadLocal在当前线程绑定一个Looper对象,而Looper持有MessageQueue对象。

    执行Handler.sendMessage(Message)方法将一个待处理的Message插入到MessageQueue中,

    这时候通过Looper.loop()方法获取到队列中Message,然后再交由Handler.handleMessage(Message)来处理。

    Android 使用handler实现线程间发送消息 (主线程 与 子线程之间)、(子线程 与 子线程之间)

    一、主线程向子线程发送消息。

    主线程发送消息到异步线程,异步线程接收到消息后在再发送一条消息给主线程。

    1. 初始化主线程的Handler,用来接收子线程的消息。

    2. 启动异步线程,在异步线程中创建Looper,并初始化一个异步线程的Handler。

    3. 主线程获取异步线程的Handler(这里涉及到线程间同步的知识),并向异步线程发送消息。

    4. 异步线程Handler接收到消息以后,获取主线程的Handler,并向主线程发送消息。

    5. 主线程收到异步线程发来的消息。

    一般情况下的主线程和子线程之间的通信,都是通过主线程中的handler把子线程中的message发给主线程中的looper,或者,主线程中的handler通过post向looper中发送一个runnable。looper默认存在于main线程中。

    那么子线程中没有Looper,该怎么办呢?很简单,我们可以把looper绑定到子线程中,并且创建一个handler。

    在另一个线程中通过这个handler发送消息,就可以实现子线程之间的通信了。

    32.String、StringBuffer、StringBuilder的区别

    答:1.可变与不可变

    String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。

    private final char value[];

    StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。

    char[] value;

    2.是否多线程安全

    String中的对象是不可变的,也就可以理解为常量,显然线程安全。

    AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,

    如expandCapacity、append、insert、indexOf等公共方法。

    StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。

    StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

    3.StringBuilder与StringBuffer共同点

    StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。

    抽象类与接口的其中一个区别是:抽象类中可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法;而接口中只是对方法的申明和常量的定义。

    StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer会在方法上加synchronized关键字,进行同步。

    最后,如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。

    String 类型和StringBuffer的主要性能区别:String是不可变的对象, 因此在每次对String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。

    使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。

    (1)基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。

    (2)不要使用String类的"+"来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或 StringBuilder类,这在Java的优化上是一条比较重要的原则。

    (3)为了获得更好的性能,在构造 StringBuffer 或 StringBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能。

    (4)StringBuilder一般使用在方法内部来完成类似"+"功能,因为是线程不安全的,所以用完以后可以丢弃。

    StringBuffer主要用在全局变量中。

    (5)相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多 线程模式下,才可以采用StringBuilder;否则还是用StringBuffer。

    33.冒泡排序、选择排序、二叉树排序

    答:比较相邻的元素。如果第一个比第二个大,就交换他们两个。

    冒泡排序

    对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

    针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何 一对数字需要比较。

    选择排序

    在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。

    二叉树排序

    34.异常的继承体系

    答:1. Throwable

    Throwable是 Java 语言中所有错误或异常的超类。

    Throwable包含两个子类: Error 和 Exception。它们通常用于指示发生了异常情况。

    Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。

    2. Exception

    Exception及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。

    3. RuntimeException

    RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。

    编译器不会检查RuntimeException异常。例如,除数为零时,抛ArithmeticException异常。

    RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时,倘若既"没有通过throws声明抛出ArithmeticException异常",也"没有通过try...catch...处理该异常",也能通过编译。这就是我们所说的"编译器不会检查RuntimeException异常"!

    如果代码会产生RuntimeException异常,则需要通过修改代码进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!

    4. Error

    和Exception一样,Error也是Throwable的子类。它用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。

    和RuntimeException一样,编译器也不会检查Error。

    Java将可抛出(Throwable)的结构分为三种类型:被检查的异常(Checked Exception),运行时异常(RuntimeException)和错误(Error)。

    (01) 运行时异常 定义: RuntimeException及其子类都被称为运行时异常。

    特点: Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException

    异常等,都属于运行时异常。

    虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。

    如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!

    (02) 被检查的异常

    定义: Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。

    特点: Java编译器会检查它。此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。

    被检查异常通常都是可以恢复的。

    (03) 错误

    定义: Error类及其子类。

    特点: 和运行时异常一样,编译器也不会对错误进行检查。

    当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。例如,VirtualMachineError就属于错误。

    按照Java惯例,我们是不应该是实现任何新的Error子类的!

    35.介绍封装、继承、多态

    封装:

    就是对属性和方法的信息隐藏,是利用抽象的数据类型把数据和对数据的操作封装在一起,

    只保留一些对外提供的方法,供我们使用。从而起到一个保护数据的作用,

    也就是说,我们只能通过对外提供的方法和封装的对象进行交流和交互。

    好处:

    能够减少耦合性,可以对对象的数据类型进行修改。

    继承:

    就是对自定义类继承父类,子类可以实现父类中的一些方法,也可以在子类中写我们需要的方法,这样可以减少代码的编写。

    继承分为:类继承和实现接口。

    类继承:一个类只能继承一个类(单继承)

    实现接口:必须实现接口里的所有方法,一个类可以实现多个接口

    好处:

    提高了代码的复用性。

    多态:

    一个对象在不同地方展现出的不同状态。

    可以说是方法的重写和重载、

    重写:子类实现父类中的方法,方法名,参数列表和返回值类型必须相同。

    重载:方法名相同,方法参数或参数个数不同。

    36.抽象类与接口的区别

    答:

    抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量

    必须指向实现所有接口方法的类对象。

    抽象类要被子类继承,接口要被类实现。

    接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

    抽象类里可以没有抽象方法。

    接口可以被类多实现(被其他接口多继承),抽象类只能被单继承。

    接口中没有 this 指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法。

    抽象类不能在Java 8 的 lambda 表达式中使用。

    37.接口能否写方法体?

    答: 可以,这个是Java8的新特性,Java8允许给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特性又叫做扩展方法

    38.集合子类的特点

    答: Collection

    |-----List  有序(存储顺序和取出顺序一致),可重复

    |----ArrayList ,线程不安全,底层使用数组实现,查询快,增删慢。效率高。

    每次容量不足时,自增长度的一半,如下源码可知

    int newCapacity = oldCapacity + (oldCapacity >> 1);

    |----LinkedList , 线程不安全,底层使用链表实现,查询慢,增删快。效率高

    |----Vector , 线程安全,底层使用数组实现,查询快,增删慢。效率低

    每次容量不足时,默认自增长度的一倍(如果不指定增量的话),如下源码可知

    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?

    capacityIncrement : oldCapacity);

    |-----Set   元素唯一

    一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。

    |--HashSet 底层是由HashMap实现的,通过对象的hashCode方法与equals方法来保证插入元素的唯一性,无序(存储顺序和取出顺序不一致)。

    |--LinkedHashSet 底层数据结构由哈希表和链表组成。哈希表保证元素的唯一性,链表保证元素有序。(存储和取出是一致)

    |--TreeSet 基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。 元素唯一。

    Map:

    |---1、HashMap

    特点:保存元素时先进后出、无序性;查询效率比较高;key-value可以为null,但最多只能为一个null;不支持线程同步,即可以有多个线程同时写HashMap,可能导致数据不一致,如果需要同步可以使用Collection的synchronizedMap方法使其同步。

    |---2、LinkedHashMap

    特点:LinkedHashMap内部是双向链表结构,保存了元素插入的顺序,Iterator遍历元素时按照插入的顺序排列,支持线程同步。

    |---3、TreeMap

    特点:保存元素key-value不能为null,允许key-value重复;遍历元素时随机排列。

    39.List集合有几种排序方式,Set集合有几种排序方式,Map集合有几种排序方式?

    答:List集合本身就是有序的和可重复的,可以使用connections工具类对其进行排序,通过实现Comparable接口,来对集合中的自定义对象排序,通过实现Comparator接口,来对集合中的自定义对象排序,该方式相对于上一种方式优势在于可以按多种规则排序,Set集合的排序只有后面的两种,Map集合的排序方式

    有Comparator可以对集合对象或者数组进行排序的比较器接口,实现该接口的public compare(T o1,To2)方法即可实现排序,该方法主要是根据第一个参数o1,小于、等于或者大于o2分别返回负整数、0或者正整数;是对根据TreeMap的key值来进行排序的,但是有时我们需要根据TreeMap的value来进行排序。对value排序我们就需要借助于Collections的sort(List list, Comparator c)方法,该方法根据指定比较器产生的顺序对指定列表进行排序。但是有一个前提条件,那就是所有的元素都必须能够根据所提供的比较器来进行比较。

    40. HashMap如何保证key的唯一性?

    答:添加的元素都有一个 hashCode(哈希值),他们先比较哈希值,是否相同?

    不相同的元素,添加进入 HashTable.   如果hashCode相同的话, 再去比较 equals()方法,

    如果也相同的话,JVM就认为数据已经存在了,就不会添加数据!

    41. 介绍OSI参考模型和TCP/IP参考模型

    答:应用层->包含大量人们普遍需要的协议

    表示层->用于完成某些特定功能

    会话层->允许不同机器上的用户之间建立会话关系

    传输层->实现网络中不同主机上的用户进程之间可靠的数据通信

    网络层->完成网络中主机间的报文传输

    数据链路层->如何在不可靠的物理线路上进行数据的可靠传输

    物理层->完成相邻结点之间原始比特流的传输

    TCP/IP 4层

    应用层->处理高层协议

    传输层->在源结点和目的结点的两个进程实体之间提供可靠的,端到端的数据传输

    互连网层->处理上层发送请求,处理输入数据报,处理ICMP报文

    网络接口层->涉及分组与网络接口

    42. 如何解决并发线程的数据安全?

    答:①:在共享数据前加volatile修饰,且保证对共享数据的操作要是原子性的

    ②:java.util.concurrent.atomic下的AtomicInteger等类来包装共享数据

    ③:锁如静态锁synchronize、重入锁ReentrantLock等

    43.  序列化机制和原理

    答: 序列化就是将对象转化为字节流,反序列化就是将字节流转化为对象,默认的序列化是深度的序列化,静态成员不会被默认序列化,要让一个类支持序列化只要让这个类实现Serializable接口即可,Serializable只是一个没有定义任何方法的接口,声明实现Serializable接口后保存读取对象就可以使用ObjectOutputStream,ObjectInputStream流了,ObjectOutputStream是OutputStream的子类,但实现了ObjectOutput接口,ObjectOutput是DataOutput的子接口,增加了一个writeObject()方法将对象转化为字节写到流中,ObjectInputStream是InputStream的子类,实现了ObjectInput接口,ObjectInput是DataInput的子接口,增加了一个readObject()方法从流中读取字节码转化为对象,常见的String ,Date,Double,ArrayList,

    LinkedList,HashMap,TreeMap等都默认实现了Serializable.

    44.java如何进行序列化和反序列化?

    答: 序列化实现步骤:

    (1)需要序列化的对象所属类必须实现Serializable接口;

    (2)构造FileOutputStream对象;

    (3)构造ObjectOutputStream对象;

    (4)使用ObjectOutputStream对象的writeObject()方法进行序列化;

    (5)关闭ObjectOutputStream对象;

    (6)关闭FileOutputStream对象;

    (7)对序列化全程捕获IOException;

    反序列化实现步骤:

    (1)需要序列化的对象所属类必须实现Serializable接口;

    (2)构造FileInputStream对象;

    (3)构造ObjectInputStream对象;

    (4)使用ObjectInputStream对象的readObject()方法进行序列化;

    (5)关闭ObjectInputStream对象;

    (6)关闭FileInputStream对象;

    (7)对序列化全程捕获ClassNotFoundException和IOException;

    45.Http和Https的区别

    答:

    46.TCP和UDP的区别

    答:

    47.如何获取IP地址信息?

    答:一般获取客户端的IP地址的方法是:request.getRemoteAddr();但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实IP地址了。

    可以通过X-Forwarded-For头获取,但是值并不止一个,而是一串IP值  多次反向代理后会有多个ip值,第一个ip才是真实ip

    48.Socket如何保持长连接

    答:方法1:应用层自己实现的心跳包

    由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer事件中定时 向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没 有收到服务器的心跳包,则认为连接不可用。

    方法2:TCP的KeepAlive保活机制

    因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多 且稍显复杂,而利用TCP/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。 不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。但是

    开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启KeepAlive功 能

    KeepAlive设置不合理时可能会 因为短暂的网络波动而断开健康的TCP连接

    49.Socket、TCP/IP、HTTP的区别

    答:

    1、TCP/IP是个协议组,可分为三个层次:网络层、传输层和应用层。

    在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。

    在传输层中有TCP协议与UDP协议。

    在应用层有FTP、HTTP、TELNET、SMTP、DNS等协议。

    因此,HTTP本身就是一个协议,是从Web服务器传输超文本到本地浏览器的传送协议。

    2、HTTP协议是建立在请求/响应模型上的。首先由客户建立一条与服务器的TCP链接,并发送一个请求到服务器,请求中包含请求方法、URI、协议版本以及相关的MIME样式的消息。服务器响应一个状态行,包含消息的协议版本、一个成功和失败码以及相关的MIME式样的消息。

    HTTP/1.0为每一次HTTP的请求/响应建立一条新的TCP链接,因此一个包含HTML内容和图片的页面将需要建立多次的短期的TCP链接。一次TCP链接的建立将需要3次握手。

    另外,为了获得适当的传输速度,则需要TCP花费额外的回路链接时间(RTT)。每一次链接的建立需要这种经常性的开销,而其并不带有实际有用的数据,只是保证链接的可靠性,因此HTTP/1.1提出了可持续链接的实现方法。HTTP/1.1将只建立一次TCP的链接而重复地使用它传输一系列的请求/响应消息,因此减少了链接建立的次数和经常性的链接开销。

    3、结论:虽然HTTP本身是一个协议,但其最终还是基于TCP的。不过,目前,有人正在研究基于TCP+UDP混合的HTTP协议。

    4.Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

    50.  Socket如何实现流量控制和拥塞控制的实现机制

    答:1. 利用滑动窗口实现流量控制

    如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收

    利用滑动窗口机制可以很方便地在TCP连接上实现对发送方的流量控制。

    设A向B发送数据。在连接建立时,B告诉了A:“我的接收窗口是 rwnd = 400 ”(这里的 rwnd 表示 receiver window) 。因此,发送方的发送窗口不能超过接收方给出的接收窗口的数值。

    TCP的拥塞控制

    拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。

    几种拥塞控制方法

    慢开始( slow-start )、拥塞避免( congestion avoidance )、快重传( fast retransmit )和快恢复( fast recovery )。

    51.多线程如何同步

    答:

    1.同步方法

    即有synchronized关键字修饰的方法。

    由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,

    内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

    2.同步代码块

    即有synchronized关键字修饰的语句块。

    被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。

    通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

    3.使用特殊域变量(volatile)实现线程同步

    a.volatile关键字为域变量的访问提供了一种免锁机制,

    b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,

    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值

    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

    注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。

    用final域,有锁保护的域和volatile域可以避免非同步的问题。

    4.使用重入锁实现线程同步

    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。

    ReentrantLock类是可重入、互斥、实现了Lock接口的锁,

    它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力

    ReenreantLock类的常用方法有:

    ReentrantLock() : 创建一个ReentrantLock实例

    lock() : 获得锁

    unlock() : 释放锁

    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

    注:关于Lock对象和synchronized关键字的选择:

    a.最好两个都不用,使用一种java.util.concurrent包提供的机制,

    能够帮助用户处理所有与锁相关的代码。

    b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码

    c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁

    5.使用局部变量实现线程同步

    如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

    ThreadLocal 类的常用方法

    ThreadLocal() : 创建一个线程本地变量

    get() : 返回此线程局部变量的当前线程副本中的值

    initialValue() : 返回此线程局部变量的当前线程的"初始值"

    set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

    注:ThreadLocal与同步机制

    a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。

    b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式

    6.使用阻塞队列实现线程同步

    前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。

    使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。

    本小节主要是使用LinkedBlockingQueue来实现线程的同步

    LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。

    队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~

    LinkedBlockingQueue 类常用方法

    LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue

    put(E e) : 在队尾添加一个元素,如果队列满则阻塞

    size() : 返回队列中的元素个数

    take() : 移除并返回队头元素,如果队列空则阻塞

    注:BlockingQueue定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:

    add()方法会抛出异常

    offer()方法返回false

    put()方法会阻塞

    7.使用原子变量实现线程同步

    需要使用线程同步的根本原因在于对普通变量的操作不是原子的。

    那么什么是原子操作呢?

    原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作

    即-这几种行为要么同时完成,要么都不完成。

    在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,

    使用该类可以简化线程同步。

    其中AtomicInteger表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

    AtomicInteger类常用方法:

    AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger

    addAddGet(int dalta) : 以原子方式将给定值与当前值相加

    get() : 获取当前值

    52.tcp连接建立的时候3次握手的具体过程

    答:TCP是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换 TCP窗口大小信息。

    第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;

    第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;

    第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。

    为什么要三次握手?

    为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

    具体例子:“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”

    53.tcp断开连接的具体过程

    答:

    当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的啊。那对于TCP的断开连接,这里就有了神秘的“四次分手”。

    第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;

    第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;

    第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;

    第四次分手主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

    为什么要四次分手?

    TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。

    54.socket关闭释放网络资源的时候,数据还未传输完毕,如何解决?

    答:解决方案一:

    传一个文件,建立一个连接,传完文件后,立即关闭连接。

    解决方案二:

    在传一个文件之前,先传一个文件长度过去,再传文件的数据,传完了关闭连接。

    接收端,先接收文件长度,然后,按照文件长度来读取数据,

    当文件长度读取完毕后,等待下次传递。

    55. 什么是请求队列?

    答:请求队列是一个双向链表,其元素就是请求描述符(也就是request数据结构)。请求队列描述符中的queue_head字段存放链表的头(第一个伪元素),而请求描述符中queuelist字段的指针把任一请求链接到链表的前一个和后一个元素之间。

    56.常用的HTTP方法有哪些?

    答:GET

    描述:从服务器获取一份文档,不需要主体

    HEAD

    描述:只从服务器获取文档首部,不需要主体

    POST

    描述:向服务器发送需要处理的数据,需要主体

    PUT

    描述:将请求的主体内容存在服务器上,需要主体

    TRACE

    描述:对可能经过代理服务器传送到服务器上的报文进行追踪,不需要主体

    OPTIONS

    描述:可以决定在服务器上可以执行哪些方法,不需要主体

    DELETE

    描述:从服务器上删除一份文档,不需要主体

    57. HTTP请求报文与响应报文格式

    答:HTTP请求报文格式:

    HTTP请求报文主要由请求行、请求头部、请求正文3部分组成

    1,请求行

    由3部分组成,分别为:请求方法、URL(见备注1)以及协议版本,之间由空格分隔

    请求方法包括GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE以及扩展方法,当然并不是所有的服务器都实现了所有的方法,部分方法即便支持,处于安全性的考虑也是不可用的

    协议版本的格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1

    2,请求头部

    请求头部为请求报文添加了一些附加信息,由“名/值”对组成,每行一对,名和值之间使用冒号分隔

    常见请求头如下:

    请求头

    说明

    Host

    接受请求的服务器地址,可以是IP:端口号,也可以是域名

    User-Agent

    发送请求的应用程序名称

    Connection

    指定与连接相关的属性,如Connection:Keep-Alive

    Accept-Charset

    通知服务端可以发送的编码格式

    Accept-Encoding

    通知服务端可以发送的数据压缩格式

    Accept-Language

    通知服务端可以发送的语言

    请求头部的最后会有一个空行,表示请求头部结束,接下来为请求正文,这一行非常重要,必不可少

    3,请求正文

    可选部分,比如GET请求就没有请求正文

    GET请求示例:

    POST请求示例:

    HTTP响应报文格式:

    HTTP响应报文主要由状态行、响应头部、响应正文3部分组成

    1,状态行

    由3部分组成,分别为:协议版本,状态码,状态码描述,之间由空格分隔

    状态代码为3位数字,200~299的状态码表示成功,300~399的状态码指资源重定向,400~499的状态码指客户端请求出错,500~599的状态码指服务端出错(HTTP/1.1向协议中引入了信息性状态码,范围为100~199)

    这里列举几个常见的:

    状态码

    说明

    200

    响应成功

    302

    跳转,跳转地址通过响应头中的Location属性指定(JSP中Forward和Redirect之间的区别)

    400

    客户端请求有语法错误,不能被服务器识别

    403

    服务器接收到请求,但是拒绝提供服务(认证失败)

    404

    请求资源不存在

    500

    服务器内部错误

    2,响应头部

    与请求头部类似,为响应报文添加了一些附加信息

    常见响应头部如下:

    响应头

    说明

    Server

    服务器应用程序软件的名称和版本

    Content-Type

    响应正文的类型(是图片还是二进制字符串)

    Content-Length

    响应正文长度

    Content-Charset

    响应正文使用的编码

    Content-Encoding

    响应正文使用的数据压缩格式

    Content-Language

    响应正文使用的语言

    响应示例:

    58.常见的HTTP相应状态码

    答:

    200– 服务器成功返回网页 404 – 请求的网页不存在 503 – 服务不可用

    1xx(临时响应)

    表示临时响应并需要请求者继续执行操作的状态代码。

    代码   说明

    100   (继续) 请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。

    101   (切换协议) 请求者已要求服务器切换协议,服务器已确认并准备切换。

    2xx (成功)

    表示成功处理了请求的状态代码。

    代码   说明

    200   (成功)  服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。

    201   (已创建)  请求成功并且服务器创建了新的资源。

    202   (已接受)  服务器已接受请求,但尚未处理。

    203   (非授权信息)  服务器已成功处理了请求,但返回的信息可能来自另一来源。

    204   (无内容)  服务器成功处理了请求,但没有返回任何内容。

    205   (重置内容) 服务器成功处理了请求,但没有返回任何内容。

    206   (部分内容)  服务器成功处理了部分 GET 请求。

    3xx (重定向)

    表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。

    代码   说明

    300   (多种选择)  针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。

    301   (永久移动)  请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。

    302   (临时移动)  服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。

    303   (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。

    304   (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。

    305   (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。

    307   (临时重定向)  服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。

    4xx(请求错误)

    这些状态代码表示请求可能出错,妨碍了服务器的处理。

    代码   说明

    400   (错误请求) 服务器不理解请求的语法。

    401   (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。

    403   (禁止) 服务器拒绝请求。

    404(未找到) 服务器找不到请求的网页。

    405   (方法禁用) 禁用请求中指定的方法。

    406   (不接受) 无法使用请求的内容特性响应请求的网页。

    407   (需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。

    408   (请求超时)  服务器等候请求时发生超时。

    409   (冲突)  服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。

    410   (已删除)  如果请求的资源已永久删除,服务器就会返回此响应。

    411   (需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。

    412   (未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。

    413   (请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。

    414   (请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。

    415(不支持的媒体类型) 请求的格式不受请求页面的支持。

    416   (请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。

    417   (未满足期望值) 服务器未满足”期望”请求标头字段的要求。

    5xx(服务器错误)

    这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。

    代码   说明

    500(服务器内部错误)  服务器遇到错误,无法完成请求。

    501   (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。

    502   (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。

    503   (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。

    504   (网关超时)  服务器作为网关或代理,但是没有及时从上游服务器收到请求。

    505   (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。

    RFC 6585 最近刚刚发布,该文档描述了 4 个新的 HTTP 状态码。

    HTTP 协议还在变化?是的,HTTP 协议一直在演变,新的状态码对于开发 REST 服务或者说是基于 HTTP 的服务非常有用,下面我们为你详细介绍这四个新的状态码以及是否应该使用。

    428 Precondition Required (要求先决条件)

    先决条件是客户端发送 HTTP 请求时,如果想要请求能成功必须满足一些预设的条件。

    一个好的例子就是 If-None-Match 头,经常在 GET 请求中使用,如果指定了 If-None-Match ,那么客户端只在响应中的 ETag 改变后才会重新接收回应。

    先决条件的另外一个例子就是 If-Match 头,这个一般用在 PUT 请求上用于指示只更新没被改变的资源,这在多个客户端使用 HTTP 服务时用来防止彼此间不会覆盖相同内容。

    当服务器端使用 428 Precondition Required 状态码时,表示客户端必须发送上述的请求头才能执行请求,这个方法为服务器提供一种有效的方法来阻止 'lost update' 问题。

    429 Too Many Requests (太多请求)

    当你需要限制客户端请求某个服务数量时,该状态码就很有用,也就是请求速度限制。

    在此之前,有一些类似的状态码,例如 '509 Bandwidth Limit Exceeded'. Twitter 使用 420 (这不是HTTP定义的状态码)

    如果你希望限制客户端对服务的请求数,可使用 429 状态码,同时包含一个 Retry-After 响应头用于告诉客户端多长时间后可以再次请求服务。

    431 Request Header Fields Too Large (请求头字段太大)

    某些情况下,客户端发送 HTTP 请求头会变得很大,那么服务器可发送 431 Request Header Fields Too Large 来指明该问题。

    59.HTTP1.1版本新特性

    答:1、默认持久连接,

    节省通信量,只要客户端和服务端任意一端没有明确的断开TCP连接,就可以发送多次HTTP请求

    2、管线化

    客户端可以同时发送多个HTTP请求,而不用一个个等待响应

    3、断点续传原理

    其原理是:客户端记录下当前的下载进度,并在需要续传时通知服务器本次需要下载的内容片断

    一个简单的断点续传实现如下:(HTTP1.1协议中定义了断点续传相关的属性,如Range和Content-Range)

    当客户端下载一个1024K文件,下载到500k时网络中断

    当客户端请求续传时,客户端需要在http头中声明本次需要续传的片断,如下:

    Range: bytes=500000——这个头通知服务器从文件的500k位置开始传文件

    服务器收到断点续传请求后,从文件的500k位置开始传输文件,并在http头开始增加Content-Range:bytes=500000-1024000

    并且此时服务器端返回的HTTP状态码应该是206,而不是200

    实际上,会出现一种请况,就是在终端发起续传请求时,URL对应的文件内容在服务器端已经发生变化,因此续传的数据肯定是错误的,解决方法如下:

    怎样判断文件是否已发生修改呢?

    我们先了解一下http它的二次请求原理:

    当客户端第一次请求资源时,服务器除了返回本次请求额资源以外,还在头信息中返回几个重要属性:

    ①、Last-Modified // 最后修改时间如: Last-Modified     Thu, 26 Nov 2009 13:50:19 GMT

    ②、Etag // 指示资源的状态唯一标识如:Etag     “8fb8b-14-4794674acdcc0″

    当同一个用户第二次请求该文件时,客户端会把上一次服务器返回来的Last-Modified和Etag的值发送给服务器,形式如下:

    If-Modified-Since   Thu, 26 Nov 2009 13:50:19 GMT

    If-None-Match       ”8fb8b-14-4794674acdcc0″

    服务器会判断该文件从上次时间到现在都没有过修改或者Etag信息没有变化,

    服务器判断发送过来的Etag和计算出来的Etag匹配,因此If-None-Match为False,不返回200,返回304,并返回Etag,Etag值不变,客户端继续使用本地缓存,

    因此,有两种方法判断文件是否发生修改:

    1、可以Last-modified来识别文件的最后修改时间,

    2、可以使用Etag给文件设置唯一标识

    一般我们都是用Etag来判断文件是否修改,用Etag来解决Last-modified不能解决的问题,原因如下:

    1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;

    2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录

    MTIME只能精确到秒)

    3、某些服务器不能精确的得到文件的最后修改时间;

    60. 常见HTTP首部字段

    答:

    HTTP报文结构

    报文首部

    空行(CR+LF)

    报文主体

    HTTP报文类型

    请求报文

    响应报文

    HTTP请求报文首部

    请求行

    请求首部字段

    通用首部字段

    实体首部字段

    HTTP响应报文首部

    状态行

    响应首部字段

    通用首部字段

    实体首部字段

    HTTP一共有四种类型的首部字段

    通用首部字段:请求报文和响应报文两方都会使用的首部。

    请求首部字段:从客户端向服务器发送请求报文时使用的首部。

    响应首部字段:从服务器向客户端返回响应报文时使用的首部。

    实体首部字段:针对请求报文和响应报文的实体部分使用的首部

    61. Http与Https优缺点?

    答:a、通信使用明文不加密,内容可能被窃听,也就是被抓包分析。

    b、不验证通信方身份,可能遭到伪装

    c、无法验证报文完整性,可能被篡改

    HTTPS就是HTTP加上加密处理(一般是SSL安全通信线路)+认证+完整性保护

    62. 什么是Http协议?

    答:

    63. 说一下Http协议中302状态

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

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

    64. Http协议有什么组成?

    答: HTTP协议采用了请求/响应模型。

    客户端向服务器发送一个请求,请求头包含请求的方法、URL、协议版本、以及包含请求修饰符、

    客户信息和内容的类似于MIME的消息结构。

    服务器以一个状态行作为响应,响应的内容包括消息协议的版本,

    成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容。

    65. Http协议中Http1.0与1.1区别?

    答:在http1.0中,当建立连接后,客户端发送一个请求,服务器端返回一个信息后就关闭连接,当浏览器下次请求的时候又要建立连接,显然这种不断建立连接的方式,会造成很多问题。

    1,HTTP/1.0协议使用非持久连接,即在非持久连接下,一个tcp连接只传输一个Web对象,;

    2,HTTP/1.1默认使用持久连接(然而,HTTP/1.1协议的客户机和服务器可以配置成使用非持久连接)。

    在持久连接下,不必为每个Web对象的传送建立一个新的连接,一个连接中可以传输多个对象!

    66. Http优化?

    答:利用负载均衡优化和加速HTTP应用

    利用HTTP Cache来优化网站

    67. Http协议有那些特征?

    答:HTTP协议的主要特点可概括如下:

    1.支持客户/服务器模式。2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

    68. 什么是比特(Bit),什么是字节(Byte),什么是字符(Char),它们长度是多少,各有什么区别

    答:Bit最小的二进制单位 ,是计算机的操作部分 取值0或者1

    Byte是计算机操作数据的最小单位由8位bit组成 取值(-128-127)

    Char是用户的可读写的最小单位,在Java里面由16位bit组成 取值(0-65535)

    Bit 是最小单位 计算机 只能认识 0或者1

    8个字节 是给计算机看的字符 是看到的东西  一个字符=二个字节

    69. 什么是流,按照传输的单位,分成哪两种流,并且他们的父类叫什么流是指数据的传输

    答:

    字节流,字符流

    字节流:InputStream OutputStream

    字符流:Reader Writer

    输入输出相对于程序

    输入流InputStream

    ,输出流OutputStream

    节点流,处理流

    节点流:OutputStream

    处理流: OutputStreamWriter

    70. BufferedReader属于哪种流,它主要是用来做什么的,它里面有那些经典的方法

    答:属于处理流中的缓冲流,可以将读取的内容存在内存里面,有readLine()方法

    71. 什么是节点流,什么是处理流,它们各有什么用处,处理流的创建有什么特征

    答:

    节点流 直接与数据源相连,用于输入或者输出

    处理流:在节点流的基础上对之进行加工,进行一些功能的扩展

    处理流的构造器必须要 传入节点流的子类

    72. 如果我要打印出不同类型的数据到数据源,那么最适合的流是那个流,为什么

    答:Printwriter 可以打印各种数据类型

    73. 怎么样把我们控制台的输出改成输出到一个文件里面,这个技术叫什么

    答:SetOut(printWriter,printStream)重定向

    74. 怎么样把输出字节流转换成输出字符流,说出它的步骤

    答:使用 转换处理流OutputStreamWriter 可以将字节流转为字符流

    New OutputStreamWriter(new FileOutputStream(File file));

    75. 把包括基本类型在内的数据和字符串按顺序输出到数据源,或者按照顺序从数据源读入,一般用哪两个流

    答:DataInputStream DataOutputStream

    76. 把一个对象写入数据源或者从一个数据源读出来,用哪两个流

    答:ObjectInputStream ObjectOutputStream

    77. 如果在对象序列化的时候不想给一个字段的数据保存在硬盘上面,采用那个关键字?

    答:transient关键字

    78. 在实现序列化接口是时候一般要生成一个serialVersionUID字段,它叫做什么,一般有什么用

    答:是版本号,要保持版本号的一致 来进行序列化

    为了防止序列化出错

    79. InputStream里的read()返回的是什么,read(byte[] data)是什么意思,返回的是什么值

    答:返回的是所读取的字节的int型(范围0-255)

    read(byte [ ] data)将读取的字节储存在这个数组

    返回的就是传入数组参数个数

    Read  字节读取字节  字符读取字符

    80. OutputStream里面的write()是什么意思,write(byte b[], int off, int len)这个方法里面的三个参数分别是什么意思

    答:write将指定字节传入数据源

    Byte b[ ]是byte数组

    b[off]是传入的第一个字符

    b[off+len-1]是传入的最后的一个字符

    len是实际长度

    81. 流一般需要不需要关闭,如果关闭的话在用什么方法,一般要在那个代码块里面关闭比较好,处理流是怎么关闭的,如果有多个流互相调用传入是怎么关闭的?

    答:流一旦打开就必须关闭,使用close方法

    放入finally语句块中(finally 语句一定会执行)

    调用的处理流就关闭处理流

    多个流互相调用只关闭最外层的流

    82. 说说你对io流的理解

    答:字节输入流 InputStream

    字节输出流 OutputStream

    字符输入流 Reader

    字符输出流 Writer

    所有流都是这四个流的子类

    说下常用的io流

    InputStream,OutputStream,

    FileInputStream,FileOutputStream,

    BufferedInputStream,BufferedOutputStream

    Reader,Writer

    BufferedReader,BufferedWriter

    相关文章

      网友评论

        本文标题:java面试

        本文链接:https://www.haomeiwen.com/subject/qwomwxtx.html