笔记之_Java整理IO流

线程重复执行:
ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
        exec.scheduleAtFixedRate(线程名, 1, 1, TimeUnit.SECONDS);
字符输出流writer写完后要执行flush()方法,不然内容不存在
IO流:实时交互
Socket网络交互
多线程和socket合为一体
所有软件几乎都会有多线程,所有跟网络有关的必须要使用socket
大多数的程序只能循环运行单独的程序块,但无法同时运行不同的多个程序块
Poc、计算器、记事本等都是单线程
Java支持多线程
进程:一般是对操作系统而言的(windows、linux/Android、Unix/ios)
所有的应用程序都要运行在进程中
    分类:
        前台进程:有界面,用户能感知
        后台进程:无界面,但是后台运行(升级、maven)
线程:在应用程序中去开辟
    多线程:含义是指在单个进程中,可以同时运行不同的线程,执行不同任务
            共享同一块内存空间和同一组系统资源,有可能相互影响
            多线程的执行策略是抢占式策略
            一般由exe、虚拟机执行
    单线程:同一时间,一个进程只执行一个线程,无论这个线程多复杂
进程是由操作系统来管理的,而线程则是在一个程序中(进程中)
线程优先级为十个等级:默认为5,标准普通,值越大,优先级越高
线程一词来源于linux
Java实现线程:
        继承Thread类(Thread类实现了Runnable的方法)
            实现Runnable做公共类,使用AtomicInteger定义变量
        实现Runnable接口
            Thread可用静态变量int
优先级低不代表抢不到资源,而是抢到的几率小一点
多线程执行的顺序无法保证,每次顺序不一样
接口在实现未实现的方法可以被new
并发变量AtomicInteger:
做操作时其他线程等待
get()取值,set(int)设值
getandIncrement()取得之后再加一
AtomicInteger  i=new AtomicInteger(20);
并发:同一时间多个方法对同一个变量进行操作
一个进程new完后只能调用一次start(),后面会报错
线程启动用start(),如果用run()则调用的是一个普通方法
同步:为了防止多个线程同时访问同一个数据对象,对象数据造成损坏
防止同步:排队,一个一个对共享资源进行操作,而不是同时操作

Java线程同步方法:synchronized
    同步方法
        public synchronized void test() {
}
    同步代码块
        private static Object obj=new Object();
public void test() {
    synchronized(obj){
    }
}
或者
public void test() {
    synchronized(this.class){
    }
}
相对而言同步方法安全性要高些

死锁:同步的线程分别占用地方需要的资源不放弃,都在等待对方放弃自己需要的同步资源,从而形成线程的死锁
死锁与死循环
死锁导致的情况就是程序不执行操作了,一直在等待(不占用cpu资源)
死循环是一直在执行
Java线程通信规定使用wait()、notify()、notifyAll()
存放线程的容器叫做线程池(有序的集合,LinkedList,方便新增和删除)
线程池中的线程处于wait()状态,执行一个线程后notify()或notifyAll()
多线程中wait()、notify()、notifyAll()方法属于Object类
Wait()与sleep()区别:
Wait()线程处于等待状态,释放对象锁(放弃抢占资源),让其他线程进行运行,由notify()或notifyAll()唤醒线程
    Sleep()线程处于休眠状态,等到指定时间会自动唤醒,并且在休眠中不会释放对象锁
Yield()静态方法,放弃本次执行,让其他线程先执行
isAlive()判断线程是否还存在
join()将一个线程加入子线程
每个Java程序都会有一个主线程,多线程的实现就是在主线程main()中创建新的线程
线程的状态转换:
        创建:Thread myThread = new MyThreadClass( );
        就绪:调用start()
        运行:
        阻塞:sleep()或wait()方法时
        销毁:正常结束或强制结束(stop())
Suspend()容易出现死锁,被wait()替代了,用resume()唤醒
Native关键字,本地的,虚拟机的资源

AtomicInteger的方法:
        Set()设置值
        Get()取值
如果在方法中使用了wait()或者notify()/notifyAll()这个方法必须加synchronized关键字
CPU在同一时刻处理线程数为:cpu核数*3-1
线程池:
    含义:
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,
里面存放了众多(未死亡)的线程,池中线程执行调度由池管
理器来处理。当有线程任务时,从池中取一个,执行完成后线
程对象归池,这样可以避免反复创建线程对象所带来的性能开
销,节省了系统的资源
作用: 优化程序,节约系统的开销
线程池比较重要的类
ExecutorService: 真正的线程池接口。
ScheduledExecutorService: 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor:  ExecutorService的默认实现。
ScheduledThreadPoolExecutor  继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
配置线程池方法:
newSingleThreadExecutor 创建一个单线程的线程池
newFixedThreadPool 创建固定大小的线程池
newCachedThreadPool 创建一个可缓存的线程池
newScheduledThreadPool 创建一个大小无限的线程池
    执行用execute()
    关闭用shutdown()
线程控制方法:
start() 线程方法,新建的线程进入Runnable状态
wait()对象方法,释放对象锁,线程进入blocked状态,等待被notify或时间到期
notify()/notifyAll() 对象方法,唤醒其他的线程
yield() 线程静态方法,放弃执行,回到Runnable状态,使其他优先级不低于此线程的线程有机会先运行
sleep() 线程静态方法,线程睡眠指定的一段时间,线程进入blocked状态
join()线程方法,调用这个方法的线程,会等待加入的子线程完成
isAlive() 判断某一个线程是否还在运行run,Run方法结束返回false
    setPriority()设置线程优先级
    currentThread()获得当前线程
getId()取得线程Id
getName()取得线程名称
setName()设置线程名称

调度策略
协同式:
线程的执行时间由线程本身控制,线程把自己的工作执行完了之后,要主动通知系统切换到另一个线程上。
坏处:线程执行时间不可控制
抢占式:
高优先级的线程抢占CPU
每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定.
(线程划分为1~10等级,默认为5)
Java的调度方法
同优先级线程组成先进先出队列,使用时间片策略
对高优先级,使用优先调度的抢占式策略
多线程的优势
减轻编写交互频繁、涉及面多的程序的困难(如监听网络端口)。
程序的吞吐量会得到改善(同时监听多种设备, 如网络端口、串口、并口以及其他外设)。
有多个处理器的系统,可以并发运行不同的线程(否则,任何时刻只有一个线程在运行)。
进程和线程的不同点
进程是由操作系统来管理的,而线程则是在一个程序(进程)内。
不同进程的代码、内部数据和状态都是完全独立的,而一个程序内的多线程是共享同一块内存空间和同一组系统资源,有可能互相影响。
线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
线程池实例:
        ExecutorService  pool=Executors.newSingleThreadExecutor();
        Thread  t1=new MyThread();
        pool.execute(t1);
pool.shutdonw();
1: 线程的基本概念、线程的基本状态及状态之间的关系?
线程是比进程更小的单位,是进程内部独立的、有序的指令流。因此,一个进程能包括多个并发执行的线程。由于各个线程之间的控制流彼此独立,使得各个线程之间的代码是乱序执行的,由此带来的线程调度、同步等问题需要进行特殊处理。
线程有五种状态:新生态,可运行态,运行态,阻塞态,死亡态。
1、当利用new创建线程实例对象后,他仅仅作为一个实例存在,jvm没有为其分配CPU时间片等线程运行资源,该线程处于新生态。
2、可运行态,当线程对象调用了start()方法后,该线程处于可运行态,这时线程已经获得了除cpu之外的其他系统资源,只等jvm的线程调度管理器按照线程的优先级对该线程进行调度,从而使该现成拥有能够获取cpu时间片的机会。3、运行状态,jvm选中一个可运行状态线程,使其占有cpu并转换为运行状态。运行状态的线程执行自己的run()方法中的代码,直到调用其他方法而终止,或等待某资源而阻塞或完成任务而死亡。
4、阻塞状态,处于运行状态的线程在某些情况下,如执行了sleep()方法,或等待I/O设备资源,将让出cpu并暂时终止自己的运行,进入阻塞状态,也称为不可运行状态。出于阻塞状态的线程是不可执行的,即使cpu空闲
5、死亡状态,死亡状态是线程生命周期中最后一个阶段。导致现成死亡有两种情况:一种是线程正常完成了全部工作,即run()方法的代码。另一种是线程被强制性的终止,如通过stop()方法来终止一个线程。
2:多线程有几种实现方法,都是什么?
两种实现方法,一种是继承Thread,另外一种是实现接口Runnable。
3:同步有几种实现方法,都是什么?
同步的实现方法有两种,分别是synchronized, wait与notify。用synchronized可以对一段代码、一个对象及一个方法进行加锁。用wait与notify可以使对象处于等待及唤醒方式导致同步,因为每个对象都直接或间接的继承了Object类。
4:sleep()和wait()有什么区别?
sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监恐状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll方法)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
5:同步和异步有何异同,在什么情况下分别使用他们?举例说明。
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存储(也就是给对象资源加锁),在java中用synchronized关键字给对象、程序段、方法加锁。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回值时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
6:当一个线程进入一个对象的一个synchronized方法后,其他线程是否可以进入此对象的方法?
不能,一个对象的一个synchronized方法只能由一个线程访问。
7:简述synchronized和java.util.concurrent.locks.Lock的异同?
主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。
8:java中有几种方法可以实现一个线程?
用什么关键字修饰同步方法?
stop()和suspend()方法为何不推荐使用?
第二问就是synchronized。
下面说说第三问:
反对使用stop(),是因为它不安全。
它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改他们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()方法的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被“挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应该在自己的Thread类中置一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,使用wait()命令进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。
流:可以理解为一组有序的、有起点的和有终点的管道
输入和输出是就程序本身而言的
    输入流:读取数据到内存
    输出流:内存数据写入磁盘
流的分类:
    输入流
输出流
    或:字节流:处理图片、压缩文件、包括字符串(会乱码)
字符流:处理字符串
字节输入流:inputSteam
字节输出流:outputStream
字符输入流:Reader接口
字符输出流:Writer接口
包装类:字节流与字符流的转换

输出时new  FileOutputStream(“文件地址及名称”,true),为true时表示存在文件就追加内容,没有就新建,不写true表示存在就覆盖,不存在就新建
使用byteArrayOutputStream解决InputStream传输字符串乱码问题
后缀为dat的文件存放重要数据,可以反序列化
地址栏输入网址请求,一定是get请求
支付宝接口、微信接口
Eclips中project?clearn重新编译项目
Eclipse中项目右键?properties?Java compiler?设置编译JDK的版本
字节输入流:
    节点流类型:
            FileInputStream
            PipedInputStream
            ByteArrayInputStream
    处理流类型(包装类):
    BufferedInputStream
    SequenceInputStream
    ObjectInputStream
字节输入流方法:
    int read()
    int read(byte[]  b)
    int read(byte[],int  offset,int  length)
字符输入流:
    节点流类型:
FileReader
PipedReader
charArrayReader
    处理流类型(包装类):
        BufferedReader
        InputStreamReader
字符输入流方法:
    int read()
    int read(chare[]  b)
    int read(char[],int  offset,int  length)

字节输出流:
    节点流类型:
            FileOutputStream
            PipedOutputStream
            ByteArrayOutputStream
    处理流类型(包装类):
    BufferedOutputStream
    DateOutputStream
    ObjectOutputStream
    PrintStream
字节输出流方法:
    void write (byte[]  b)
    void write (byte[],int  offset,int  length)
字符输出流:
    节点流类型:
FileWriter
PipedWriter
charArrayWriter
    处理流类型(包装类):
        BufferedWriter
        InputStreamWriter
printWriter
字符输出流方法:
    void write ()
    void write (char[]  b)
    void write (char[],int  offset,int  length)

字节输入流:
    private static void readAll(){
        InputStream  is=null;
        try {
             is=new FileInputStream("f:\\xiaoshuo.txt");
             byte[]  data=new byte[1024];
             int len=0;
             StringBuffer str=new StringBuffer();
             while(   (len=is.read(data))!=-1  ){
                 System.out.println("本次读了长度为:"+len+"个字节的数据");

                 str.append(  new  String(data,0,len,"UTF-8")  );
             }
             System.out.println("------------读完了---------------");
             System.out.println(str.toString());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
字符输入流:
    private static void readAll(){
        Reader rd=null;
        try {
            rd=new FileReader("f:\\xiaoshuo.txt");
            char[] data=new char[1024];
            int len=0;
            StringBuffer str=new StringBuffer();
            while(  (len=rd.read(data))!=-1  ){
                 System.out.println("本次读了长度为:"+len+"个字符的数据");
                 str.append(new String(data, 0, len));
            }
            System.out.println(str.toString());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(rd!=null){
                try {
                    rd.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
利用包装类解决字节流乱码问题:
        private static void readAll(){
        InputStream  is=null;
        ByteArrayOutputStream baos=null;
        try {
             is=new FileInputStream("f:\\xiaoshuo.txt");
             baos=new ByteArrayOutputStream();
             byte[]  data=new byte[1024];
             int len=0;
             while(   (len=is.read(data))!=-1  ){
                 System.out.println("本次读了长度为:"+len+"个字节的数据");
                 baos.write(data, 0, len);
             }

             System.out.println("------------读完了---------------");
             System.out.println(  baos.toString("UTF-8"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(baos!=null){
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
输出流实例:
        private static void write1(){
        OutputStream  os=null;
        try {
            os=new FileOutputStream("f:\\11.txt",true);
            String info="\r\n这是我的四个数据";
            os.write( info.getBytes("UTF-8"));
            os.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(os!=null){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
序列化:将对象写入文件,对象要继承serializable实现接口
        private static void xulihua() {
        Student st = new Student();
        st.setId(1000);
        st.setName("测试");
        st.setSex("男");
        st.setHp(100);
        ObjectOutputStream oos = null;
        try {
            OutputStream os = new FileOutputStream("f:\\stu.dat");
            oos = new ObjectOutputStream(os);
            st.setHp(50);
            System.out.println(".....保存游戏进度......");
            System.out.println("被弄死了......");
            System.out.println("Game over.....");
            oos.writeObject(st);
            oos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
反序列化:对象不能改变
        private static void reload(){
        ObjectInputStream  ois=null;
        try {
            InputStream  in=new FileInputStream("f:\\stu.dat");
            ois=new ObjectInputStream(in);
            Student  stu=(Student) ois.readObject();
            System.out.println(stu.toString());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally{
            if(ois!=null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
网络下载器:
    public static void main(String[] args) {
            OutputStream  download=null;
            HttpURLConnection conn=null;
            try {
                URL    url=new URL
("http://dlc2.pconline.com.cn/filedown_171040_8417060/Yx2PSEuB/jpwb2017cl.exe");
                conn=(HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(3000);
                 System.out.println("响应码:"+conn.getResponseCode());
                if(conn.getResponseCode()==200){
                    InputStream  readInfo=conn.getInputStream();
                    download=new FileOutputStream("f:\\jpwb2.exe");
                    byte[]  data=new byte[1024*100];
                    int len=0;

                    while( (len=readInfo.read(data))!=-1){
                        System.out.println("从服务器上面读取了"+(len/1024.0)+"KB的数据");
                        download.write(data, 0, len);
                        download.flush();
                    }
                    System.out.println("-----文件下载成功----");
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                if(download!=null){
                    try {
                        download.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(conn!=null){
                    conn.disconnect();                }
            }
        }

线程实例Thread:
public class Test1 {
    public static void main(String[] args) {
        Test1  test=new Test1();
        MyThread1  t1=test.new MyThread1();
        t1.setPriority(10);   //优先级设这最高
        t1.start();  //启动多线程
        MyThread1  t2=test.new MyThread1();
        t2.setPriority(1);   //优先级设为最低
        t2.start();
    }
    class MyThread1 extends Thread {
        @Override
        public void run() {
            for(int i=1;i<=20;i++){
                System.out.println("线程"+this.getId()+":"+i);
            }
        }
    }
}
线程实例Runnable接口:
public class MyRunnable implements Runnable{
    public void run() {
       for(int i=1;i<=20;i++){
           System.out.println("线程"+Thread.currentThread().getId()+":"+i);
       }
    }
}
public static void main(String[] args) {
        MyRunnable myRun = new MyRunnable();
        Thread t1 = new Thread(myRun);
        t1.start();
        Thread t2 = new Thread(myRun);
        t2.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 20; i++) {
                    System.out.println("线程" + Thread.currentThread().getId()
                            + ":" + i);
                }
            }
        }).start();
    }
时间: 2024-10-05 14:00:00

笔记之_Java整理IO流的相关文章

学习笔记三十三:IO流(四)

一无所有是一种财富,它让穷人产生改变命运的行动. 本讲内容:RandomAccessFile RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了.这些记录的大小不必相同:但是其大小和位置必须是可知的.但是该类仅限于操作文件. RandomAccessFile不属于InputStream和OutputStream类系的.实际上,除了实现DataInput和DataOutput接口之外(DataInputStream和DataOu

笔记之_java整理servlet

Get请求与与post请求: Get不会改变服务器数据,只做查询 Post会对服务器数据做出修改,可评论的网页 火狐的firebug附加组件,可以测试网络 延迟实例化,提供一个set方法,让子类实现赋值 父类引用指向子类实现 Jquery easyUI中文文档 Pojo简单的实体类对象 Json Object和jsonArray不可以直接进行日期转换,List<>可以 Html5删除<!DOCTYPE HTML后的内容> 引入jquery后用$()代替了window.onload.

笔记之_java整理html5

查看手机唯一标识:http://html5plus.org/doc/zh_cn/device.html#plus.device.uuid html和html5的区别? html5+css3也逐渐的成为新一代web前端技术,最要用处在开发手机网站. 文档网站:http://www.w3school.com.cn/html5/index.asp HTML5的新功能 (1).强大的绘图功能 (2).新增音频/视频标签 (3).浏览器离线存储 (4).通过浏览器进行定位 外网连接局域网的项目:花生壳?内

笔记之_java整理框架

This依赖于对象,static依赖于类 字符串的模糊查询: instr(empName,?)>0 empName like '%白%' Create view 表名 as 多表连接 视图是临时表 多表连接语句生成,查询工具点开 <T,K>泛型可以用多个 ResultSetMetaData取得列名集合,rst.getMetaData Reflect反射,Field[] 实例化对象方法: New student(); Student.class.newInstance(); Class.f

笔记之_java整理hibernate

页面调试数据: <%@taglib uri="/struts-tags" prefix="s" %> <s:debug></s:debug> Js清除缓存 使用hibernate延迟加载时,并且数据库有关联关系,转换成json对象时不能直接转换,要用new JSONObject(),然后put()方法存值 国外框架项目地址:http://websystique.com/springmvc/spring-mvc-4-angularj

笔记之_java整理JavaScript

1.javascript 面向对象这一块 1). javascript的基本数据类型有哪些? Number (数字) 1 1.2 String (字符串) '' "" Boolean 布尔 true false Undefined 未定义 Null 空 2).javascript的引用数据类型有哪些? Object {}. 函数 function . 数组 [] 2.javascript定义对象,动态扩冲里面的变量或者属性 var a =new Object(); //实例化一个对象

笔记之_java整理kindeditor文件上传插件

下载插件压缩包 图片创建虚拟服务器路径: 在Tomcat中,打开Tomcat的系统文件夹servers,打开下面的server.xml, 在<Context docBase="class" path="/class"--/>标签中,修改绝对路径docBase和虚拟路径path ,绝对路径是磁盘上创建的保存图片的文件夹,虚拟路径是网页访问图片的路径, 根据自己要用的上传文件的类型,选择插件的相关案例,引用css.js,修改路径以及页面类型jsp.php,

笔记之_Java整理css3

微传单:http://s.wcd.im/index.jsp?id=9661Zi9&flyerAid=9661&p&loading=0&fromOfficial Friendly同包访问 子类重写方法的修饰符必须大于等于子类定义好的访问修饰符 类是抽象的对象是现实存在的 局部变量必须先赋值才能使用 一个Java文件可以包含n个类智能有一个是public并且与文件名一致 接口继承接口,方法名相同不是重写是覆盖 实现接口的类不是抽象类,则必须实现接口及其父级接口的所有方法 Str

笔记之_Java整理Spring MVC

springMVC原理讲解地址:http://www.cnblogs.com/dragonfei/p/6148625.html Ionic2和angularjs2和angularjs和nosql菜鸟教程一起是做手机的 https://www.bilibili.com/video/av8614724/index_10.html是ionic2公开课地址 FreeCMS DeDeCMS风信网,数据库表结构 DEDECMS简单建站 Filter和Interceptor的区别 Filter是基于函数回调的