Java总结

一、Java高级技术

1、IO流

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

1)、常用的阻塞式IO流:

2)、NIO:Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

//使用NIO读取数据
public class FileInputProgram {  
    static public void main( String args[] ) throws Exception {  
        FileInputStream fin = new FileInputStream("c:\\test.txt");    
        FileChannel fc = fin.getChannel();              //获取通道  
        ByteBuffer buffer = ByteBuffer.allocate(1024);  //创建缓冲区
        fc.read(buffer);                                //读取数据到缓冲区  
        buffer.flip();  
        while (buffer.remaining() > 0) {  
            byte b = buffer.get();  
            System.out.print(((char)b));  
        }  
        fin.close();
    }  
}
//使用NIO写入数据
public class FileOutputProgram {  
    static private final byte message[] = { 83, 111, 109, 101, 32, 98, 121, 116, 101, 115, 46 };
    static public void main( String args[] ) throws Exception {  
        FileOutputStream fout = new FileOutputStream( "e:\\test.txt" );  
        FileChannel fc = fout.getChannel();              //获取通道
        ByteBuffer buffer = ByteBuffer.allocate( 1024 ); //创建缓冲区
        for (int i=0; i<message.length; ++i)             //进行写数据
            buffer.put( message[i] ); 
        buffer.flip();  
        fc.write( buffer );  
        fout.close();  
    }  
}

Java NIO的工作原理:由一个专门的线程来处理所有的IO事件并负责分发;事件到的时候触发,而不是同步的去监视事件;线程之间通过wait/notify等方式通讯,保证每次上下文切换都是有意义的。其模型图如下:

image7.png | center | 689x251

3)、Socket:Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。可以理解成两个程序的交通通讯工具。

2、线程

1)、线程的创建:

  • 继承Thread类创建线程:
public class MyThread extends Thread{     //继承Thread类 重写run方法
  public void run(){     
  }
}
public class Main {
  public static void main(String[] args){
    new MyThread().start();            //创建并启动线程
  }
}
  • 实现Runnable接口创建线程:
public class MyThread2 implements Runnable {    //实现Runnable接口 重写run方法
  public void run(){
  }
}
public class Main {
  public static void main(String[] args){
        new Thread(new MyThread2()).start();    //启动线程 将Runnable作为参数传递到Thread里面
  }
}

注意,调用start方法后,该线程不一定立即就执行,调用了start方法后只是将线程状态设置为可执行状态,至于什么时候执行由操作系统决定。因为java是单继承方式,所以第一种方法有时候使用起来不是很方便,因此有了第二种使用接口的方式进行弥补而更加的灵活。使用接口的方式可以很方便的实现代码共用。如果单独调用run方法的话,这个时候并没有开启一个线程,只是调用了一个普通方法而已,只有调用了start方法后, 系统才会开启一个线程然后在这个线程中运行run里面的代码

2)、线程间通信:

  • synchronized:在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。
  • volatile:volatile关键字的作用是使变量在多个线程间可见(可见性)。在JAVA中,每个线程都有一个自己的本地内存空间(线程栈空间),线程执行时先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作,操作完后在某个时间再把变量刷新回主内存,因此在多线程操作的时候很容易出现没有及时将变量数据刷新回去而出现错误,这个时候可以用volatile修饰变量。volatile关键字修饰的变量不会被指令重排序优化,即只会在主内存中存在,不会读取到本地内存空间操作。
  • lock:lock是Java中的一种乐观锁,synchronized是关键字,由虚拟机来操作, 其实是一种悲观锁。Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
public interface Lock {
    void lock();          //获取锁,如果锁已被其他线程获取,则进行等待。
    void lockInterruptibly() throws InterruptedException;        //中断
    boolean tryLock();   //尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//同上,在拿不到锁时不会一直在那等待。
    void unlock();        //释放锁
    Condition newCondition();
}

注意,synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。

private void testMethod1(){
    Lock lock = ...;
    lock.lock();        //获取锁
    try{
        //处理任务
    }catch(Exception ex){
    }finally{
        lock.unlock();   //释放锁
    }
}
private void testMethod2(){
    Lock lock = ...;
    if(lock.tryLock()) {
        try{
            //处理任务
         }catch(Exception ex){
         }finally{
             lock.unlock();   //释放锁
         } 
    }else {
        //如果不能获取锁,则直接做其他事情
    }
}
  • sleep:是Thread的静态类方法,谁调用的谁去阻塞休眠(睡觉),即使在a线程里调用了b的sleep方法,实际上还是a去睡觉。最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  • wait/notify/notifyAll:它们属于Object基础类,也就是每个对象都有wait( ),notify( ),notifyAll( ) 的功能,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。
void notify();            //唤醒在此对象监视器上等待的单个线程
void notifyAll();         //唤醒在此对象监视器上等待的所有线程
void wait( );             //导致当前的线程等待,直到其他线程调用此对象的notify/notifyAll
void wait(long timeout);  //导致当前的线程等待,直到其他线程调用此对象的notify/notifyAll,或者指定的时间过完
void wait(long timeout, int nanos);    //导致当前的线程等待,直到其他线程调用此对象的notify/notifyAll,或者其他线程打断了当前线程,或者指定的时间过完。
  • wait:使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入”预执行队列“中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException,他是RuntimeException的一个子类,因此,不需要try-catch语句进行捕捉异常。
  • notify:该方法也要在同步方法或同步块中调用,即在调用前,线程页必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使他等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchornized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,他会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。
public class WaitNotifyTest {
    private String[] shareObj = { "true" };    //对象用来作为锁
    public static void main(String[] args) {
        WaitNotifyTest test = new WaitNotifyTest();
        ThreadWait threadWait1 = test.new ThreadWait("wait thread1");
        threadWait1.setPriority(2);
        ThreadWait threadWait2 = test.new ThreadWait("wait thread2");
        threadWait2.setPriority(3);
        ThreadWait threadWait3 = test.new ThreadWait("wait thread3");
        threadWait3.setPriority(4);
        ThreadNotify threadNotify = test.new ThreadNotify("notify thread");
        threadNotify.start();
        threadWait1.start();
        threadWait2.start();
        threadWait3.start();
    }
    class ThreadWait extends Thread {
        public ThreadWait(String name){
            super(name);
        }
        public void run() {
            synchronized (shareObj) {        //获取shareObj锁
                while ("true".equals(shareObj[0])) {
                    long startTime = System.currentTimeMillis();
                    try {
                        shareObj.wait();    //让出shareObj锁,程序将挂起,知道notify唤醒才执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println("线程" + this.getName() + "等待时间为:" + (endTime - startTime));
                }
            }
        }
    }
    class ThreadNotify extends Thread {
        public ThreadNotify(String name){
            super(name);
        }
        public void run() {
            try {
                sleep(3000);            // 给等待线程等待时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (shareObj) {    //获取shareObj锁,如果没有获取锁调用notifyAll将抛出异常
                shareObj[0] = "false";
                shareObj.notifyAll();
                System.out.println("线程" + this.getName() + "通知结束");
            }
            System.out.println("线程" + this.getName() + "运行结束");
        }
    }
}

3)、线程池:

public ThreadPoolExecutor(
    int corePoolSize,        //该线程池中核心线程的数量
    int maximumPoolSize,     //该线程池中最大线程数量
    long keepAliveTime,      //非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后则会被回收
    TimeUnit unit,           //上面时间的单位
    BlockingQueue<Runnable> workQueue,//线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务
    ThreadFactory threadFactory,     //为线程池提供创建新线程的功能,这个我们一般使用默认即可
    RejectedExecutionHandler handler //拒绝策略,当线程无法执行新任务时抛出异常通知你
    ) {
        ...
    }

线程池执行流程如下:.

  • 调用execute一个线程之后,如果线程池中的线程数未达到核心线程数,则会立马启用一个核心线程去执行。
  • 调用execute一个线程之后,如果线程池中的线程数已经达到核心线程数,且workQueue未满,则将新线程放入workQueue中等待执行
  • 调用execute一个线程之后,如果线程池中的线程数已经达到核心线程数但未超过非核心线程数,且workQueue已满,则开启一个非核心线程来执行任务
  • 调用execute一个线程之后,如果线程池中的线程数已经超过非核心线程数,则拒绝执行该任务,采取饱和策略,并抛出RejectedExecutionException异常

通过流程发现,在线程池中执行一个新的线程,在核心线程没有满的时候,会立即启动一个核心线程执行,如果核心线程已经满了,这个时候将当前任务添加到了队列中,而并没有开启非核心线程执行,只有在队列满的的情况下才开启新的非核心线程执行,而前面添加的几个线程还在队列里面,顺序是不是有点乱,我们看看workQueue有哪些种类:

  • 有界的任务队列(ArrayBlockingQueue),创建队列的时候需要指定其大小,若有新的任务要执行,如果线程池中的线程数小于核心线程数(corePoolSize),则会创建核心线程执行。若大于corePoolSize,则会将新任务加入到等待队列中,直到当前队列满,如果队列已经满了,还没有到达最大线程数,才会开启新的非核心线程执行。此队列中的任务等待中。
  • 无界的任务队列(LinkedBlockingQueue),与有界队列相比,除非系统资源耗尽,否则不存在任务入队失败的情况。若有新的任务要执行,如果线程池中的线程数小于核心线程数(corePoolSize),则会创建核心线程执行。若大于核心线程数,则会将新任务加入到等待队列中,因为当前队列无界,若任务创建和处理的速度差异很大,无界队列将保持快速增长,直到耗尽系统内存。这样的队列创建的线程就不会超过 corePoolSize,因此maximumPoolSize的值完全无效,当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列。
  • 优先任务队列(PriorityBlockingQueue),是一个特殊的无界任务队列,上面讲的有界队列和无界队列都是按照先进先出的原则进行,而优先任务队列可以根据任务自身的优先级顺序执行,并总是执行优先级最高的任务。
  • 直接提交的任务队列(SynchronousQueue),该队列没有容量,提交的任务不会被真实的保存在队列中,而总是将新任务提交给线程执行。即如果没有空闲的核心线程执行,则尝试创建新的非核心线程。如果线程数大于maximumPoolSize,则执行拒绝策略

线程池相比线程的优势:

  • 降低资源消耗,频繁的开启新线程也会耗费大量的资源
  • 提高响应速度
  • 提高线程的可管理性

4)、ExecutorService:Java通过Executors提供比较常见的四种线程池,这样就不用去对ThreadPoolExecutor设置复杂的参数

  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
/**
 * 可缓存线程池 生命周期很短暂并且需要立即响应
 * new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
 * 核心线程数为0 非核心线程数无穷大 任务队列是异步队列
 * 因此提交一个任务后会立即开启一个非核心线程执行
 */
private void testMethod(){
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        Thread.sleep(index * 1000);
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(index);
            }
        });
    }
}
  • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
/**
 * 固定数量线程 生命周期比较稳定或者比较长的任务
 * new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
 * 核心线程数和非核心线程数都为指定数量 任务队列是无界等待队列
 * 因此指定的任务数量最好超过整个应用固定线程数量
 * 除此之外需要注意一个问题 因为采用的是无界队列 所以非核心线程永远不会启动 实际上就只有指定数量的核心线程运行而已
 */
private void testMethod() {
    //创建一个大小为3的线程池 
    //定长线程池的大小最好根据系统资源进行设置,如Runtime.getRuntime().availableProcessors()
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
        final int index = i;
        fixedThreadPool.execute(new Runnable() {
            @Override
            public void run(){
                System.out.println(index);
                Thread.sleep(2000);
            }
        });
    }
}
  • newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。ScheduledExecutorService比Timer更安全,功能更强大。
private void testMethod() {
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    //延迟3秒执行任务
    scheduledThreadPool.schedule(new Runnable() {
        @Override
        public void run() {
            System.out.println("delay 3 seconds");
        }
    }, 3, TimeUnit.SECONDS);
    //延迟1秒执行周期任务 周期是3秒
    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
        @Override
	    public void run() {
		    System.out.println("delay 1 seconds, and excute every 3 seconds");
	    }
    }, 1, 3, TimeUnit.SECONDS);
}
  • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作
private void testMethod() {
    //创建一个单线程池
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        //顺序执行所有任务
        singleThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(index);
                Thread.sleep(2000);
            }
        });
    }
}

5)、Callable:

Runnable接口通常用在线程中,其实该接口实际上只是定义了一个run方法而且该方法没有参数也没用返回值,如果不不与Thread一起使用跟普通代码没有什么区别,因此可以理解为Runnable接口就是代表一段可以调用的代码,Callable接口代表一段可以调用并返回结果的代码。由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果,而Callable是一个泛型接口,call()函数返回的类型就是传递进来的V类型。其源码定义如下

public interface Runnable {
    public abstract void run();
}
public interface Callable<V> {
    V call() throws Exception;
}

6)、Future与FutureTask:

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

public interface Future<V> {
    /**
     * 取消任务 
     * @param mayInterruptIfRunning是否允许取消正在执行却没有执行完毕的任务
     * @retrun 取消已经完成的任务会返回false 
               取消任务还没有执行会返回true  
             取消的任务正在执行若参数设置为true则返回true,若参数设置为false返回false
     */
    boolean cancel(boolean mayInterruptIfRunning);
    //任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true
    boolean isCancelled();
    //任务是否已经完成,若任务完成,则返回true
    boolean isDone();
    //获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回
    V get() throws InterruptedException, ExecutionException;
    //获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask,提供了两个构造函数,一个以Callable为参数,另外一个以Runnable为参数。这些类之间的关联对于任务建模的办法非常灵活,允许你基于FutureTask的Runnable特性(因为它实现了Runnable接口),把任务写成Callable,然后封装进一个由执行者调度并在必要时可以取消的FutureTask。

FutureTask可以由执行者调度,这一点很关键。它对外提供的方法基本上就是Future和Runnable接口的组合:get()、cancel、isDone()、isCancelled()和run(),而run()方法通常都是由执行者调用,我们基本上不需要直接调用它。

//RunnableFuture接口继承了Runnable和Future
public interface RunnableFuture<V> extends Runnable, Future<V> {  
    void run();  
}  
//FutureTask实现了Runnable和Future
public class FutureTask<V> implements RunnableFuture<V>{
    //构造方法:传递一个Call参数
    public FutureTask(Callable<V> callable) {      
        if (callable == null)  
            throw new NullPointerException();  
        sync = new Sync(callable);  
    }  
    //构造方法:传递一个runnable和一个泛型
    public FutureTask(Runnable runnable, V result) {  
        sync = new Sync(Executors.callable(runnable, result));  
    }  
    //定义了一系列状态
    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;
    //拥有一个有返回值的Callable
    private Callable<V> callable;
    //用来存储结构的变量
    private Object outcome; 
    //拥有一个线程
    private volatile Thread runner;
    //等待线程的堆栈
    private volatile WaitNode waiters;
    //根据state来判断是否完成状态
    public boolean isDone() {
        return state != NEW;
    }
    //根据state来判断是否被取消状态
    public boolean isCancelled() {
        return state >= CANCELLED;
    }
    //取消任务
    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW && U.compareAndSwapInt(this, STATE, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {   
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)   t.interrupt();    //中断线程
                } finally { 
                    U.putOrderedInt(this, STATE, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }
    //获取任务执行结果
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING) s = awaitDone(false, 0L);
        return report(s);
    }
    public V get(long timeout, TimeUnit unit)  throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null) throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&  (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)  throw new TimeoutException();
        return report(s);
    }
    private V report(int s) throws ExecutionException {
        Object x = outcome;            //返回的就是outcome
        if (s == NORMAL)  return (V)x; //状态正常的话就返回outcome 不正常抛出异常
        if (s >= CANCELLED)  throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }
    //任务执行
    public void run() {
        if (state != NEW || !U.compareAndSwapObject(this, RUNNER, null,  Thread.currentThread())) return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();    //实际上还是执行的call里面的代码
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)  set(result);     //设置结果 实际上把call的结果设置给outcome
            }
        } finally {
            runner = null;
            int s = state;
            if (s >= INTERRUPTING)  handlePossibleCancellationInterrupt(s);
        }
    }
    protected void set(V v) {
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = v;
            U.putOrderedInt(this, STATE, NORMAL); // final state
            finishCompletion();
        }
    }
    protected void setException(Throwable t) {
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = t;
            U.putOrderedInt(this, STATE, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }
}

通过上面源码可以知道,FutureTask实现了Future接口,使其具有查看任务状态及获取执行结果等的功能;同时FutureTask还实现了Runnable接口,使其可以作为线程的方式或者线程池的方式进行操作;并且内部持有一个Callable接口并在run方法中被调用和获取结果,最后配合Future让使用者很方便的进行异步任务的操作。这也是一个典型的模板模式。

7)、FutureTask的三种使用方式:

  • 普通线程Thread方式:
public static void main(String[] args) throws ExecutionException, InterruptedException {
    FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>(){
        @Override
        public Integer call() throws Exception {
            return 100;
        }    
    });
    Thread thread = new Thread(futureTask);  //FutureTask作为Runnable使用
    thread.start();                          //启动线程
    if(futureTask.isDone())                  //判断FutureTask是否完成
        Integer result=futureTask .get();    //FutureTask获取结果
}
  • 线程池ExecutorService方式:
public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executor = Executors.newCachedThreadPool();    //创建一个线程池
    FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>(){
        @Override
        public Integer call() throws Exception {
            return 100;
        }    
    });
    executor.submit(futureTask);        //通过线程池submit方法提交一个FutureTask任务
    executor.shutdown();
}

3、异常

Exception和Error都是继承了Throwable类,在Java中只有Throwable类型的实例才可以被抛出throw或者捕获catch,他是异常处理机制的基本组成类型。 

  • Error是指正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(比如JVM自身)处于非正常状态,不可恢复状态。属于是非正常情况,程序无法处理的错误,一般发生后会导致线程关闭,所以不便于也不需要捕获,常见的比如OutOfMemoryError之类,都是Error的子类。 
  • Exception又分为可检查checked异常和不检查unchecked异常。可检查异常在源码里必须显示的进行捕获处理,这里是编译期检查的一部分。 不检查异常就是所谓的运行时异常,通常是可以代码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译器强制要求。

1)、异常处理的原理:Java虚拟机通过方法调用栈来跟踪每个线程中一系列的方法调用过程,如果在执行方法的过程中抛出异常,则虚拟机必须找到能捕获该段代码的catch代码块,如果虚拟机追溯到调用栈的底部仍然没有找到处理该异常的代码块就只有终止线程。

2)、try-catch-finally:

  • 在try代码块中执行了System.exit()方法终止线程,这种情况不会执行finally里面的代码块,如下:
private void testException(){
    try{
        int x=10/100;
        System.exit(0);    //终止了当前线程,所以finally里面代码都不会执行
    }catch(Exception e){
        System.out.println("testException-catch-e="+e.toString()):
    }finally{
        System.out.println("testException-finally"):
    }
}
  • 在try中执行了return,这种情况下是会执行finally里面的代码块,如下:
private void testException(){
    int a =3;
    int b =6;
    try{
        int x=100/a;
        return a+b;        //1、在try里面先执行a+b并将结果存储下来    3、执行return将刚刚计算出来的结果9作为返回值
    }catch(Exception e){
        System.out.println("触发异常");
    } finally{
        a=10;             //2、在执行a=10,将10赋给变量a     
    }
    return a+b;            
}
  • 在finally里面执行return,这种情况可能会出现异常丢失和造成一些数据不安全,因此不建议在finally里面执行return

4、注解

注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,没有加,则等于没有任何标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。

注解提供了一种元程序中的元素关联任何信息和任何元数据的途径和方法,但是注解不能影响程序代码的执行,无论增加、删除Annotation,代码都始终如一的执行。注解的本质就是一个继承了 Annotation 接口的接口,一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。

1)、元注解:

  • @Target:用于指明被修饰的注解最终可以作用的目标是谁,即你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。
  • @Retention:用于指明当前注解的生命周期。RetentionPolicy.SOURCE(当前注解编译期可见,不会写入 class 文件);RetentionPolicy.CLASS(类加载阶段丢弃,会写入 class 文件);RetentionPolicy.RUNTIME(永久保存,可以在程序运行的时候反射获取)。
  • @Inherited:用于指定是否允许子类继承该注解。
  • @Documented:用于指定是否应当被包含在 JavaDoc 文档中

2)、Java内置注解:

  • @Override:它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • @Deprecated:用于标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。
  • @SuppressWarnings :主要用来压制 java 的警告,如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。
//如果没有SuppressWarning 编译器会弹出警告Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时
//使用SuppressWarning 编译器不再检查main方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。
@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
    Date date = new Date(2018, 7, 11);    
}

3)、Android support annotations:

  • 空注解:使用@NonNull注解修饰的参数不能为null,使用@Nullable注解修饰的参数可以为null,例如:
@Nullable                                            
public String getAndSetName(@NonNull String name){ //参数不能传递null,否则编译器发出警告
    ...省略代码...
    return null;                                   //返回值@Nullable可以返回null
}
  • 资源类型注解:为了防止我们在使用程序资源的时候,错误传递资源类型给函数,导致程序错误!常用的资源注解有@StringRes(字符串资源)、@AnimRes(动画资源)、@AnyRes(任何资源)、@IdRes(id资源)、@ColorRes、@DrawableRes、@LayoutRes、@StyleRes、@XmlRes(xml资源)等
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        testDo(R.style.AppTheme);    //参数不符合要求    编译器将报警告
        testDo(R.string.app_name);   //参数是String资源  编译器通过
    }
    private void testDo(@StringRes int str){
        Log.e("tag","-------->"+getString(str));
    }
}
  • 线程注解:用于指定方法在哪种线程中执行处理,如果和指定的线程不一致抛出异常。常用的线程注解有@UiThread(UI线程)、@MainThread(主线程)、@WorkerThread(子线程)、@BinderThread(绑定线程)
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        testDo(R.string.app_name);    //该方法指定了在子线程中执行,因此这时编译器会提示错误
    }
    @WorkerThread
    private void testDo(@StringRes int str){
        Log.e("tag","-------->"+getString(str));
    }
}
  • @CallSuper注解:用来强调在覆盖父类方法时,需要实现父类的方法,及时调用对应的super方法。被该注解修饰了的方法,如果子类覆盖的后没有实现对呀的super方法会抛出异常。
public class CallSuperT {
    @CallSuper
    protected  void init(){    //该方法被CallSuper修饰,子类重写此方法的时候一定要调用当前方法
    }
    class T extends CallSuperT{
        @Override
        protected void init() {
            super.init();      //必须要调用父类的该方法,否则编译器提示错误
        }
    }
}

5、类加载

类加载器ClassLoader就是用来动态加载class文件到虚拟机中的以便应用程序调用。Java的类加载设计了一套双亲代理的模式,使得用户没法替换系统的核心类,从而让应用更安全。所谓双亲代理就是指,当加载类的时候首先去Bootstrap中加载类,如果没有则去Extension中加载,如果再没有才去AppClassLoader中去加载。从而实现安全和稳定

1)、类加载器分类:

  • BootstrapClassLoader:引导类加载器 ,用来加载Java的核心库,即 JAVA_HOME\lib 目录下的类。通过底层代码来实现的,使用C++语言实现,属于虚拟机自身的一部分,不是ClassLoader的子类。
  • ExtClassLoader:拓展类加载器 ,用来加载Java的拓展的类库, 即${JAVA_HOME}/jre/lib/ext/ 目录中的所有jar。由Java语言实现,独立于JVM外部,并且继承自抽象类ClassLoader。
  • AppClassLoader:系统类加载器 (不要被名字给迷惑),用来加载Java应用中的类。一般来说自己写的类都是通过这个加载的。而Java中 ClassLoader.getSystemClassLoader() 返回的就是AppClassLoader。(Android中修改了ClassLoader的逻辑,返回的会是一个PathClassLoader)。由Java语言实现,独立于JVM外部,并且继承自抽象类ClassLoader。
  • 自定义ClassLoader:用户如果想自定义ClassLoader的话,只需要继承自 java.lang.ClassLoader 即可。为什么需要进行自定义类加载器,一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

注意,在Android中ClassLoader主要有两个直接子类,叫做 BaseDexClassLoader 和 SecureClassLoader 。而前者有两个直接子类是 PathClassLoader 和 DexClassLoader。

PathClassLoader用来加载安装了的应用中的dex文件,它也是Android里面的一个最核心的ClassLoader了,相当于Java中的那个AppClassLoader。在ActivityThread启动时发送一个BIND_APPLICATION消息后在handleBindApplication中创建ContextImpl时调用LoadedApk里面的getResources(ActivityThread mainThread)最后回到ActivityThread中又调用LoadedApk的 getClassLoader 生成的,具体的在LoadedApk的createOrUpdateClassLoaderLocked

DexClassLoader是一个可以用来加载包含dex文件的jar或者apk文件的,但是它可以用来加载非安装的apk。比如加载sdcard上面的,或者NetWork的。比如现在很流行的插件化/热补丁,其实都是通过DexClassLoader来实现的。具体思路是: 创建一个DexClassLoader,通过反射将前者的DexPathList跟系统的PathClassLoader中的DexPathList合并,就可以实现优先加载我们自己的新类,从而替换旧类中的逻辑了。

2)、双亲委托模式:如果一个类加载器收到了 Class 加载的请求,它首先不会自己去尝试加载这个 Class ,而是把请求委托给父加载器去完成,依次向上。因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的 Class 时,即无法完成该加载,子加载器才会尝试自己去加载该 Class 。

3)、类加载过程:

  • 加载:程序运行之前jvm会把编译完成的.class二进制文件加载到内存,并在堆中生成一个代表这个类的对象,作为对这些方法区访问的入口。这里也可以看出java程序的运行并不是直接依靠底层的操作系统,而是基于jvm虚拟机。如果没有类加载器,java文件就只是磁盘中的一个普通文件。
  • 连接:连接是很重要的一步,过程比较复杂,分为以下三步:

验证:确保类加载的正确性。一般情况由javac编译的class文件是不会有问题的,但是可能有人的class文件是自己通过其他方式编译出来的,这就很有可能不符合jvm的编 译规则,这一步就是要过滤掉这部分不合法文件。

准备:为类的静态变量分配内存,将其初始化为默认值 。我们都知道静态变量是可以不用我们手动赋值的,它自然会有一个初始值 比如int 类型的初始值就是0 ;boolean类型初始值为false,引用类型的初始值为null 。 这里注意,只是为静态变量分配内存,此时是没有对象实例的。

解析:把类中的符号引用转化为直接引用(将符号引用转化为指针或者地址)。解释一下符号引用和直接引用。比如在方法A中使用方法B,A(){B());},这里的B()就是符号引用,初学java时我们都是知道这是java的引用,以为B指向B方法的内存地址,但是这是不完整的,这里的B只是一个符号引用,它对于方法的调用没有太多的实际意义,可以这么认为,他就是给程序员看的一个标志,让程序员知道,这个方法可以这么调用,但是B方法实际调用时是通过一个指针指向B方法的内存地址,这个指针才是真正负责方法调用,他就是直接引用。

  • 初始化:为类的静态变量赋予正确的初始值,上述的准备阶段为静态变量赋予的是虚拟机默认的初始值,此处赋予的才是程序编写者为变量分配的真正的初始值。

6、反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。

1)、反射的流程:

在类的加载过程中,虚拟机加载的是.class文件的二进制数据,并在堆中生成了代表这个类的class对象。在程序运行状态的时候,虚拟机获取这个.class文件的二进制,并通过这个class对象来获取这个类的各种信息。

2)、反射获取类对象:

获取Class类的三种方式,方式一对象都有了还要反射干什么;方式二需要导入类的包,依赖太强,不导包就抛编译错误;一般都用方式三,一个字符串可以传入也可写在配置文件中等多种方法。在运行期间,一个类只有一个Class对象产生,因此上面不论哪种方式获取出来的class对象都是相等的。

package fanshe;
public class Fanshe {
	public static void main(String[] args) {
		//方式一:通过Object.getClass()方法获取
		Student stu1 = new Student();        //这一new 产生一个Student对象,一个Class对象。
		Class stuClass = stu1.getClass();    //获取Class对象
		//方式二:任何数据类型(包括基本数据类型)都有一个“静态”的class属性
		Class stuClass2 = Student.class;		
		//方式三:通过Class类的静态方法forName(String className)
		try {
			Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
		} catch (ClassNotFoundException e) {
		}
	}
}

3)、反射实例化类:

  • 使用Class对象的newInstance()方法,这种方法实际上是使用默认的构造器起来创建该类的实例
  • 使用Class对象获取指定的Constructor对象,调用Constructor对象的newInstance()方法来获取来创建该Class的实例,这样可以根据参数类型来指定使用哪个构造器
protected void onCreate(Bundle savedInstanceState) {
    try {
        Class perClas = Class.forName("shen.com.testproject.Person"); //获取类
        //方式一:无参方式实例化
        Person person0=(Person) perClas.newInstance();                //实例化方式一
        person0.setName("无参");
        //方式二:通过获取公有有参构造方法进行实例化
        Constructor cons1 = perClas.getConstructor(String.class);    //通过参数获取公有构造方法
        Person person1=(Person) cons1.newInstance("参一");            //实例化方式二
        //方式二:通过获取私有有参构造方法进行实例化
        Constructor cons2 = perClas.getDeclaredConstructor(int.class,String.class);//通过参数获取所有对应的构造方法 包括私有
        cons2.setAccessible(true);                                    //暴力访问(忽略掉访问修饰符)
        Person person2=(Person) cons2.newInstance(20,"参二");         //实例化方式二
    }catch (Exception e){}
}        

4)、反射调用方法:

由Class对象可以获得该Class的Method对象,调用Method对象的invoke()方法可以调用该Method

public void testMethod(){
    Class perClas = Class.forName("shen.com.testproject.Person");
    Constructor cons2 = perClas.getDeclaredConstructor(int.class,String.class);
    cons2.setAccessible(true);
    Person person2=(Person) cons2.newInstance(20,"参二");

    Method method=perClas.getDeclaredMethod("calData",boolean.class);//获取方法 参数1方法名 后面的参数分别为方法参数类型
    int data1=(int)method.invoke(person2,true);  //调用上面获取的方法,并传递参数
    int data2=(int)method.invoke(person2,false); //注意调用私有方法之前一定要进行
暴力设置
}
//Person类定义
public class Person {
    private int     age;
    private String  name;
    public Person(){
    }
    public Person(String name){
        this.name=name;
    }
    private Person(int age,String name){
        this.age=age;
        this.name=name;
    }
    public int calData(boolean isMan){
        if(isMan)
            return age;
        else
            return -age;
    }
}

5)、安卓中反射的应用:

  • 通过原始的反射机制方式调用资源
  • Activity的启动过程中Activity对象的创建
//ActivityThread文件里面 
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...省略代码...
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            //进行创建Activity
            activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e))   throw new RuntimeException(  "Unable to instantiate activity " + component  + ": " + e.toString(), e);
        }
        ...省略代码...
}
public Activity newActivity(ClassLoader cl, String className,  Intent intent)  throws InstantiationException, IllegalAccessException,  ClassNotFoundException {
        //通过反射方式进行实例化
        return (Activity)cl.loadClass(className).newInstance();    
}
  • XML布局文件中的视图视图View都是通过反射方式创建

二、网络协议

1、URI/URL

1)、URI:uniform resource identifier,统一资源标识符,用来唯一标识一个资源,代表一种标准。其示例如下:

2)、URL:uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,是URL的一个子集。除了是一个URI之外还要说明如何访问这个资源(http://)。大家把浏览器地址栏里访问网站的地址认为是URL就好了,也就是以HTTP/HTTPS开头的URI子集,当然还有其他协议开头的。

3)、URI的通用结构:

scheme:[// [user:password @] host [:port]] [/] path [?查询] [#片段]

URI中的scheme表示资源的命名机制,后面依次表示存放资源的主机名,资源自身的路径及名称。着重强调与资源的命名,其中的scheme也可能省略表示相对路径。

URL中的scheme表示资源访问的协议,后面依次表示存放该资源的主机IP地址及端口号,主机资源的具体地址。着重强调资源的路径,其中的scheme不能省略。

2、HTTP

1)、Http协议:超文本传输协议http是一种通信协议,它允许将超文本标记语言html文档从web服务器传送到客户端的浏览器

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

2)、Http请求消息:包括请求行(request line)、请求头部(header)、空行和请求数据四个部分组成

GET请求消息示例:

GET /562f25980001b1b106000338.jpg HTTP/1.1
Host    img.mukewang.com
User-Agent    Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept    image/webp,image/*,*/*;q=0.8
Referer    http://www.imooc.com/
Accept-Encoding    gzip, deflate, sdch
Accept-Language    zh-CN,zh;q=0.8

  • 请求行:GET说明请求类型为GET,/562f25980001b1b106000338.jpg为要访问的资源(注意:GET方式可以将请求参数&方式拼接在URL里面,这里可以解析出来),该行的最后一部分说明使用的是HTTP1.1版本
  • 请求头:从第二行起为请求头部,HOST将指出请求的目的地,User-Agent指定那些服务器端和客户端脚本都能访问它(是浏览器类型检测逻辑的重要基础)
  • 空行:请求头后面的空行必须加上,用来隔开请求头与请求主体
  • 请求主体:请求的数据也叫主体BODY,可以添加任意的其他数据。当前示例请求主体为空

POST请求消息示例:

POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alive

name=Professional%20Ajax&publisher=Wiley
  • 请求行:POST说明请求类型为POST,该行的最后一部分说明使用的是HTTP1.1版本
  • 请求头:请求头部,第二行至第六行
  • 空行:第七行的空行
  • 请求主体:name=Professional%20Ajax&publisher=Wiley就是请求主体body部分。通常GET方式将请求的数据拼接在URL中,从请求行提取出来,但是会导致数据长度要在url最大长度范围内;POST方式将请求的数据放在body中,这里存放的数据长度可以很大

3)、Http响应消息:包括状态行、消息报头、空行和响应正文四个部分组成

HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8

<html>
      <head></head>
      <body>
            <!--body goes here-->
      </body>
</html>
  • 状态行:(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)
  • 消息报头:用来说明客户端要使用的一些附加信息。Date表示生成响应的日期和时间;Content-Type指定了MIME类型的HTML(text/html)编码类型是UTF-8
  • 空行:消息报头后面的空行是必须的
  • 响应正文:服务器返回给客户端的文本信息,这里空行后面的html部分为响应正文。

4)、Http工作原理:HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。以下是 HTTP 请求/响应的步骤:

  • 客户端连接到Web服务器:一个HTTP客户端通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接
  • 发送HTTP请求:通过TCP套接字客户端向Web服务器发送一个文本的请求报文
  • 服务器接受请求并返回HTTP响应:Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取
  • 释放连接TCP连接:若connection 模式为close,则服务器主动关闭TCP连接客户端被动关闭连接,释放TCP套接字;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
  • 客户端浏览器解析HTML内容:客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示

5)、GET/POST区别:

  • GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditPosts.aspx?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的Body中
  • GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制
  • GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值
  • GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码

3、Cookie

由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证,这样服务器就能从通行证上确认客户身份了。Cookie就是这样的一种机制,它可以弥补HTTP协议无状态的不足。

1)、Cookie的机制:

本质上cookies就是http的一个扩展。有两个http头部是专门负责设置以及发送cookie的,它们分别是Set-Cookie以及Cookie。当服务器返回给客户端一个http响应信息时,其中如果包含Set-Cookie这个头部时,意思就是指示客户端建立一个cookie,并且在后续的http请求中自动发送这个cookie到服务器端,直到这个cookie过期。如果cookie的生存时间是整个会话期间的话,那么浏览器会将cookie保存在内存中,浏览器关闭时就会自动清除这个cookie。另外一种情况就是保存在客户端的硬盘中,浏览器关闭的话,该cookie也不会被清除,下次打开浏览器访问对应网站时,这个cookie就会自动再次发送到服务器端。一个cookie的设置以及发送过程分为以下四步:

  • 客户端发送一个http请求到服务器端
  • 服务器端发送一个http响应到客户端,其中包含Set-Cookie头部
  • 客户端发送一个http请求到服务器端,其中包含Cookie头部
  • 服务器端发送一个http响应到客户端

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

2)、Cookie的属性:

  • String name:该Cookie的名称。Cookie一旦创建,名称便不可更改
  • Object value:该Cookie的值。如果值为Unicode字符需要为字符编码;如果值为二进制数据则需要使用BASE64编码
  • int maxAge:该Cookie失效的时间。如果为正数则该Cookie在>maxAge秒之后失效;如果为负数该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie;如果为0表示删除该Cookie。单位秒,默认为–1
  • boolean secure:该Cookie是否仅被使用安全协议传输。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密,默认为false
  • String path:该Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie;如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”
  • String domain:可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie,而baidu.com的都不能访问。注意第一个字符必须为“.”
  • String comment:该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明
  • int version:该Cookie使用的版本号。0表示遵循Netscape的Cookie规范;1表示遵循W3C的RFC 2109规范
Cookie cookie = new Cookie("time","20080808"); // 新建Cookie
cookie.setDomain(".helloweenvsfei.com");       // 设置域名
cookie.setPath("/");                           // 设置路径
cookie.setMaxAge(Integer.MAX_VALUE);           // 设置有效期 永久有效
cookie.setSecure(true);                        // 设置安全属性
response.addCookie(cookie);                    // 输出到客户端

3)、Cookie的特性:

  • 不可垮域名性:Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作Google的Cookie而不会操作Baidu的Cookie,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google与Baidu的域名不一样,因此Google不能操作Baidu的Cookie。需要注意的是,虽然网站images.google.com与网站www.google.com同属于Google,但是域名不一样,二者同样不能互相操作彼此的Cookie。用户登录网站www.google.com之后会发现访问images.google.com时登录信息仍然有效,而普通的Cookie是做不到的。这是因为Google做了特殊处理。
  • 编码格式:Cookie中保存中文只能编码一般使用UTF-8编码即可;如果需要保存二进制数据需要BASE64编码即可。Cookie中使用数字证书提供安全度可使用对二进制数据进行BASE64编码,保存图片的时候也可以将图片转换成BASE64编码。由于浏览器每次请求服务器都会携带Cookie,因此Cookie内容不宜过多,否则影响速度,Cookie的内容应该少而精。
  • 不支持修改删除:Cookie并不提供修改、删除操作。如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。如果要删除某个Cookie,只需要新建一个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie。
  • 安全性:HTTP协议不仅是无状态的,而且是不安全的,如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。注意,secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。

4、Session

除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

1)、Session的流程:

要使用Session,第一步当然是创建Session了。那么Session在何时创建呢?当然还是在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同创建Session的方法,而在Java中是通过调用HttpServletRequest的getSession方法(使用true作为参数)创建的。在创建了Session的同时,服务器会为该Session生成唯一的Session id,而这个Session id在随后的请求中会被用来重新获得已经创建的Session;在Session被创建之后,就可以调用Session相关的方法往Session中增加内容了,而这些内容只会保存在服务器中,发到客户端的只有Session id;当客户端再次发送请求的时候,会将这个Session id带上,服务器接受到请求之后就会依据Session id找到相应的Session,从而再次使用之。正式这样一个过程,用户的状态也就得以保持了。

2)、Session的使用:

Session对应的类为javax.servlet.http.HttpSession类。每个来访者对应一个Session对象,所有该客户的状态信息都保存在这个Session对象里。Session对象是在客户端第一次请求服务器的时候创建的。Session也是一种key-value的属性对,通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。

Servlet中必须使用request来编程式获取HttpSession对象,通过request.getSession()方法获取该客户的Session,request还可以使用getSession(boolean create)来获取Session。区别是如果该客户的Session不存在,request.getSession()方法会返回null,而getSession(true)会先创建Session再将Session返回。例如:

HttpSession session = request.getSession();           // 获取Session对象
session.setAttribute("loginTime", new Date());        // 设置Session中的属性
out.println("登录时间为:" +(Date)session.getAttribute("loginTime")); // 获取Session属性

JSP中内置了Session隐藏对象,可以直接使用。如果使用声明了<%@page session="false" %>,则Session隐藏对象不可用。下面的例子使用Session记录客户账号信息:

<%@ page language="java" pageEncoding="UTF-8"%>
<jsp:directive.page import="com.helloweenvsfei.sessionWeb.bean.Person"/>
<jsp:directive.page import="java.text.SimpleDateFormat"/>
<jsp:directive.page import="java.text.DateFormat"/>
<jsp:directive.page import="java.util.Date"/>
<%!
    DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");  // 日期格式化器
%>
<%
    ...省略代码...
    if(request.getMethod().equals("POST")){ 
        session.setAttribute("person", person);                 // 保存登录的Person
        session.setAttribute("loginTime", new Date());          // 保存登录的时间              
        response.sendRedirect(request.getContextPath() + "/welcome.jsp");
    }
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
<html>
</html>

3)、Session的生命周期:

Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session,也可以使用request.getSession(true)强制生成Session。

Session生成后只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。

服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改;Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。

4)、Session和Cookie混合使用:

虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持,这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户。该Cookie为服务器自动生成的,它的maxAge属性一般为–1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。

5)、Session和Cookie区别:

  • cookie数据存放在客户的浏览器上,session数据放在服务器上
  • cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session
  • session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能。为减轻服务器性能应当使用cookie
  • cookie单个在客户端的限制是3K,就是说一个站点在客户端存放的cookie不能超过3K
  • Cookie和Session的方案虽然分别属于客户端和服务端,但是服务端的session的实现对客户端的cookie有依赖关系的,上面我讲到服务端执行session机制时候会生成session的id值,这个id值会发送给客户端,客户端每次请求都会把这个id值放到http请求的头部发送给服务端,而这个id值在客户端会保存下来,保存的容器就是cookie,因此当我们完全禁掉浏览器的cookie的时候,服务端的session也会不能正常使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

诸神黄昏EX

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

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

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

打赏作者

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

抵扣说明:

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

余额充值