反射、IO/NIO

一、反射

1、前言

反射是框架的灵魂

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)

要想更清楚了了解,应该要知道一些类加载的过程,假如有个Student类:当我们new Student时,jvm会去本地磁盘找到Student.class文件,并加载到jvm内存中,此时在jvm里其实创建了两个对象,一个是Student对象,一个是记载了Student类的一些信息的class对象。这个class对象是jvm自动创建的,且必须创建并且只会在第一次创建,即有且只有一个。

而反射的本质就是得到这个class对象,反向获取Student类的各种信息。

2、Java反射机制的作用

在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的方法

3、哪里用到反射机制

JDBC中,利用反射动态加载了数据库驱动程序。
Web服务器中利用反射调用了Sevlet的服务方法。
Eclispe等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。
很多框架都用到反射机制,注入属性,调用方法,如Spring。

4、反射机制的优缺点

优点:

  • 可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。

缺点:

  • 对性能有影响,这类操作总是慢于直接执行java代码。
  • 使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了。
  • 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用:代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化
  • 反射破坏了代码的封装性。

5、如何使用Java的反射

在这里插入图片描述

  • 1、获取Class对象
    • Class<?> clazz = Class.forName(“全限类名”);
      • 扩展:Class 的静态 forName() 方法有两个版本,上面的代码是只指定类名称的版本,而另一个版本可以让你指定类名称、加载时是否运行静态区块、指定类加载器:forName(String name, boolean initialize, ClassLoader loader)。
    • 类名.class;
    • 对象.getClass();
  • 2、获取构造器对象
    • 所有公有构造方法:Constructor[] conArray = clazz.getConstructors();
    • 所有的构造方法:Constructor[] conArray = clazz.getDeclaredConstructors();
    • 获取公有无参构造方法:Constructor con = clazz.getConstructor(null);
      • null可写可不写,它代表的是参数的类型
    • 获取私有构造方法并使用:Constructor con = clazz.getDeclaredConstructor(int.class);
      • con.setAccessible(true);//忽略修饰符
      • con.newInstance(18);//创建了一个构造方法的声明类的新实例对象
      • clazz.newInstance();//通过class对象创建一个实例对象
  • 3、获取成员变量
    • 获取所有公有的字段,包括父类中的字段:Field[] fieldArray = clazz.getFields();
    • 获取所有的字段,不包括父类中的字段:Field[] fieldArray = clazz.getDeclaredFields();
    • 获取公有字段:Field f = clazz.getField(“name”);
    • 获取私有字段并使用:Field f = clazz.getDeclaredField(“age”);
      • f.setAccessible(true);
      • Object obj = clazz.getConstructor().newInstance();//创建Student对象
      • f.set(obj, 17);//给age赋值
      • Student stu = (Student)obj;//转型
  • 4、获取成员方法
    • 获取所有公有方法,包括父类的:Method[] methodArray = clazz.getMethods();
    • 获取所有方法,不包括父类的:Method[] methodArray = clazz.getDeclaredMethods();
    • 获取公有的showName(String name)方法:Method method = clazz.getMethod(“showName”, String.class);
    • 获取私有的showAge(int age)方法:Method method = clazz.getDeclaredMethod(“showAge”, int .class);
      • method.setAccessible(true);
      • Object obj = clazz.getConstructor().newInstance();
      • Object result = method.invoke(obj, 20);//执行方法,返回结果
  • 5、其他用法
    • 使用反射时,可以把类名和方法名写在配置文件内动态读取,这样在想调整反射的类时轻而易举。
    • 越过泛型检查:泛型检查只在编译期。如果有个String类型的ArrayList< String> list,在list.add(100)是不可以的,但是可以通过反射获取list的类和add方法,然后在调用methodAdd.invoke(list, 100);添加时完全可以的。
  • 6、判断是否为某个类的实例
    • instanceof
    • isInstance()

6、反射修改常量

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Java反射修改基本类型的常量时不可靠(尽管内存中已经被修改了)。
对于基本类型的静态常量,JAVA在编译的时候就会把代码中对此常量中引用的地方替换成相应常量值。

比如
System.out.println(Bean.INT_VALU);
在编译的时候已经被java自动优化成这样的:
System.out.println(100);

这本身是JVM的优化代码提高运行效率的一个行为,但是就会导致我们在用反射改变此常量值时出现类似不生效的错觉。

二、序列化

  • 前言:
    • 数据传输过程中,都会默认采用二进制文件的方式,因为计算机的底层识别方式就是二进制,不依赖任何运行环境或是程序设计语言,所以这是实现数据传输跨平台跨网络的基础。
    • 序列化可以直接将java对象转化为一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象(反序列化)。
    • 这一过程甚至可以通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。
  • 1、对象序列化:将对象中的数据编码为字节序列的过程。
  • 2、反序列化;将对象的编码字节重新反向解码为对象的过程。
  • 3、JAVA提供了API实现了对象的序列化和反序列化的功能,使用这些API时需要遵守如下约定:
    • 被序列化的对象类型需要实现序列化接口,此接口是标志接口,没有声明任何的抽象方法,JAVA编译器识别这个接口,自动的为这个类添加序列化和反序列化方法。
    • 为了保持序列化过程的稳定,建议在类中添加序列化版本号。
    • 不想让字段放在硬盘上就加transient
  • 4、以下情况需要使用 Java 序列化:
    • 想把内存中的对象状态保存到一个文件中或者数据库中的时候;
    • 想用套接字在网络上传送对象的时候;
    • 想通过RMI(远程方法调用)传输对象的时候。

三、IO/NIO

1、前言

IO是什么:I是Input,O是Output,它是比较广义的概念,比如对操作系统而言,键盘是输入,显示器是输出。IO其实可以理解为信息的输入输出。

在这里只说Java里的IO流。流的概念不好说,大概理解为流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。

流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

首先要明确一点,IO流是相对内存来说的,把硬盘里的文件读取到内存中,就叫输入, 把内存中的数据写入硬盘中保存,就叫输出。

Java的IO模型设计非常优秀,它使用Decorator(装饰者)模式,按功能划分Stream,您可以动态装配这些Stream,以便获得您需要的功能。例如,您需要一个具有缓冲的文件输入流,则应当组合使用FileInputStream和BufferedInputStream。

网上找了一些结构图
在这里插入图片描述
在这里插入图片描述

2、IO流的分类

  • 按照处理数据类型的不同分为:字符流和字节流
  • 按照数据流向不同分为:输入流和输出流
  • 按照流的角色划分:节点流和处理流

字节流:多用于读取或书写二进制数据,这些类的基类为InputStream或OutputStream。可以处理所有以bit为单位储存的文件,也就是说可以处理所有的文件,但是在处理字符上的速度不如字符流。

字符流:目的的是为了支持Unicode编码,用于字符国际化,一个字符占用两个字节,这些类的基类为Reader或Writer。该流只能处理字符,但处理字符速度很快

节点流:文件(File),管道(Piped)和数组(Array)(他们每个类都分别包括输入输出和字节字符四种流);

处理流:其余的都是处理类,他们都是属于节点流的装饰类:
在这里插入图片描述

3、Java IO中常用的类

在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable。

  • File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
  • InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有字节输入流的父类。定义了所有输入流都具有的共同特征。
  • OutputStream(二进制格式操作):抽象类,基于字节的输出操作,是所有字节输出流的父类。定义了所有输出流都具有的共同特征。
  • Reader(文件格式操作):抽象类,基于字符的输入操作。
  • Writer(文件格式操作):抽象类,基于字符的输出操作。
  • RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object。它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。

4、IO流分项解析

1)、File

File是文件和目录路径名称的抽象表示形式,File只关注文件本身的信息,例如:文件名、路径、可读、可写等,不会关注文件的内容。常用的方法:

  • File f1=new File(“FileTest1.txt”); //创建文件对象f1,f1所指的文件是在当前目录下创建的FileTest1.txt
  • File f2=new File(“D:\dir1",“FileTest2.txt”) ;// 注意:D:\dir1目录事先必须存在,否则异常
  • File f4=new File("\dir3");File f5=new File(f4,“FileTest5.txt”); //在如果 \ \dir3目录不存在使用f4.mkdir()先创建
  • mkdir(),创建单个目录;
  • mkdirs(),创建多个目录;
  • getPath(),获取文件的路径;
  • length(),获取文件的长度;
  • getName()获取文件名字;
  • getParentFile(),获取文件的父路径名字,即获取这个文件的上一层目录;
  • exists(),判断文件是否存在;
  • createNewFile(),创建文件,是创建文件,不是目录;
  • list(),返回指定的目录里面包含的文件和目录,返回一个字符串数组;
  • listFiles(),返回一个抽象路径名数组,也就是包含文件目录和文件目录的抽象路径,通过getName()和getAbsolutePath()来获取名字和路径;
  • delete(),删除此文件或目录。
2)、输入字节流InputStream

在这里插入图片描述

  • ByteArrayInputStream、StringBufferInputStream(上图的StreamBufferInputStream)、FileInputStream是三种基本的介质流,它们分别从Byte数组、StringBuffer、和本地文件中读取数据。
  • PipedInputStream是从与其它线程共用的管道中读取数据。
  • ObjectInputStream和所有FilterInputStream的子类都是装饰流(装饰器模式的主角)。
    在这里插入图片描述
  • 流结束的判断:方法read()的返回值为-1时;readLine()的返回值为null时。
  • void close() :关闭输入流,释放和这个流相关的系统资源。
  • booleanmarkSupported() :测试当前流是否支持mark和reset方法。如果支持,返回true,否则返回false。
  • void mark(int readlimit) :在输入流的当前位置放置一个标记,如果读取的字节数多于readlimit设置的值,则流忽略这个标记。
  • void reset() :返回到上一个标记。
  • int available() :返回在不发生阻塞的情况下,可读取的字节数。
  • long skip(long n):在输入流中跳过n个字节,并返回实际跳过的字节数。
3)、输出字节流OutputStream

在这里插入图片描述
在这里插入图片描述

  • void flush() :刷新输出流,强制缓冲区中的输出字节被写出。
4)、字节流其他
  • 实例
    在这里插入图片描述
  • BuffereInputStream和BuffereOutputStream 缓冲流
    • BuffereInputStream(带有缓冲区的字节输入流)继承于FilterInputStream,而FilterInputStream继承于InputStream,而FilterInputStream只是个“装饰器模式”的封装,也就是说它并没有给出具体的功能实现,它具体的功能实现都是通过它的子类来实现的。
    • 可以将数据流从数据源中处理完毕都存入内存缓冲区,然后统一一次性与底层IO进行操作,可以有效降低程序直接操作IO的频率,提高IO执行速度。
    • 主要起到的就是一个缓冲的作用,如果直接让文件或程序跟内存进行交互,效率是十分低下的,而通过缓冲流进行交互,能够大大提高效率。
    • 利用缓冲流进行拷贝,可以多个字节多个字节拷贝
      在这里插入图片描述
5)、字符输入流Reader

在这里插入图片描述

  • CharReader、StringReader是两种基本的介质流,它们分别将Char数组、String中读取数据。
  • PipedReader是从与其它线程共用的管道中读取数据。
  • BufferedReader很明显就是一个装饰器,它和其子类负责装饰其它Reader对象。
  • FilterReader是所有自定义具体装饰流的父类,其子类PushbackReader对Reader对象进行装饰,会增加一个行号。
  • InputStreamReader是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream转变为Reader的方法。
    在这里插入图片描述
6)、字符输出流Writer

在这里插入图片描述
在这里插入图片描述

7)、字符流其他
  • 实例
    在这里插入图片描述
  • InputStreamReader和OutputStreamWriter(转换流),字节流读取转为字符流操作,后续操作同上。
    InputStreamReader isr = new InputStreamReader(new FileInputStream(“H:\01.txt”));
  • BufferedReader和BufferedWriter(字符缓冲流)
    在这里插入图片描述
8)RandomAccessFile(随机访问文件)

该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。

RandomAccessFile 是任意位置进入文件的意思,适用于由大小已知的记录组成的文件,它有一个seek方法定义了文件的位置,所以要注意在对文件进行RandomAccessFile操作时,要记住文件的内容的位置和大小,否则会发生内容复写更改的后果。

RandomAccessFile对文件进行读和写操作,具体是读还是写,要根据设置的模式(构造时的参数 “r” “rw”)来决定。底层实际可以理解为一个byte数组,是带有指针的,通过指针的指向对文件进行读操作和写操作。

特点:

  • 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
  • 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)
  • 该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。
  • 可以用于多线程下载或多个线程同时写数据到文件。

常用方法:

  • getFilePointer(),返回此文件中的当前偏移量,即返回指针位置;
  • readLine(),从此文件读取文本的下一行;
  • seek(long pos),设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作,即设置指针位置,下次读写从该位置开始;
  • skipBytes(int n),跳过输入的n个字节,丢弃跳过的字节;

直接从某一位置开始读取数据,即跳过某一位置之前,使用seek:
在这里插入图片描述

9)、其他流
  • ObjectInputStream和ObjectOutputStream(对象流)
    • 对象输入流继承于InputStream。
    • 对象流是将对象的基本数据和图形实现持久存储。ObjectOutputStream实际是在对流进行序列化操作,ObjectInputStream实际是在对流进行反序列化操作,要实现序列化,必须实现Serializable接口。
    • 如果对象中的属性加了transient和static关键字的话,则该属性不会被序列化。
      在这里插入图片描述
  • PrintStream和PrintWriter(打印流)
    • PrintStream(可以将字节流封装成打印流)继承于FilterOutputStream,FilterOutputStream是继承于OutputStream的;
    • PrintWriter(可以将字节流、字符流封装成打印流)继承于Writer的。
    • 首先请问java的标准输入流是什么?是InputStream,正确。那么java的标准输出流是什么?是OutputSteam?No!而是PrintStream。
      • 因为标准输入输出流是System类的定义,System中有三个字段,in是InputStream类型,对应的是标准输入流,err和out都是PrintStream对象,out对应的是标准输出流。我们常用的System.out.println(data)方法的返回值就是PrintStream对象,此流默认输出在控制台,也可以重定向输出位置;
      • 标准输入流本为:控制台->程序,重新定义后为文件->程序
        在这里插入图片描述
      • 标准输出流:程序->控制台,重新定义后:程序->文件
        在这里插入图片描述
    • PrintWriter就是PrintStream的字符操作的版本。用法相似!
  • ByteArrayInoutStream和ByteArrayOutputStream(内存流)
    • ByteArrayInputStream(内存输入流)继承于InputStream,ByteArrayOutputStream(内存输出流)继承于OutputStream。内存流是关不掉的,一般用来存放一些临时性的数据,理论值是内存大小。
    • ByteArrayInputStream bais = new ByteArrayInputStream(“我是内存流测试”.getBytes());//参数为字符数组
  • DataOutputStream 和 DataInputStream
    • 这一对类可以直接写入java基本类型数据(没有String),但写入以后是一个二进制文件的形式,不可以直接查看。
    • 是常用的过滤流类,如果对象的序列化是整个对象转换为一个字节序列的话,DataOutputStream / DataInputStream就是将字段序列化,转为二进制数据。
10)、字节流和字符流的区别
  • 节流没有缓冲区,是直接输出的,而字符流是输出到缓冲区的。
    • 因此在输出时,字节流不调用colse()方法时,信息已经输出了,而字符流只有在调用close()方法关闭缓冲区时,信息才输出。
    • 要想字符流在未关闭时输出信息,则需要手动调用flush()方法。
  • 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
  • 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
  • 只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流。
11)、异常

IOException异常类的子类

  • EOFException : 非正常到达文件尾或输入流尾时,抛出这种类型的异常。
  • FileNotFoundException: 当文件找不到时,抛出的异常。
  • InterruptedIOException: 当I/O操作被中断时,抛出这种类型的异常。

四、IO和NIO

1、前言

IO流大体分为BIO,AIO,NIO,常用的基本就是BIO,也就是上文所介绍的那些。

传统IO是靠字节或字符来传输,它是同步阻塞型IO。

  • 好处是代码比较简单,直观。
  • 缺点则是IO效率和扩展性存在局限性,容易成为应用性能的瓶颈。

Java1.4中引入了NIO。
NIO是靠块传输,也就是一个一个的buffer,速度相对于较快。可以构建多路复用的,同步非阻塞型IO程序,同时提供了更接近操作系统底层的高性能数据操作方式。多适用于进行流畅的网络读写操作。

Java1.7中NIO有了进一步的改进,也就是NIO2,引入了异步非阻塞IO方式(A(Asynchronous)IO)。异步IO操作基于事件和回调机制,可以简单的理解为,应用操作直接返回,而不会阻塞在哪里,当后台处理完成,操作系统会通知相应线程进行后续工作。

阻塞:在读取输入流或者写入输出流时,在读写动作完成之前,线程会一直阻塞在哪里,无法从事其他任务。
同步:后续的任务等待当前调用返回,才会进行下一步

2、IO与NIO区别

  • IO是面向流的,NIO是面向缓冲的
    • IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。
    • NIO将数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
  • IO是阻塞的,NIO是非阻塞的
  • IO无选择器,NIO有选择器
    • NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。

3、NIO核心

  • Channels(通道)
    • 通常来说NIO中的所有IO都是从 Channel(通道) 开始的。
  • Buffers(缓冲区)
    • Buffer本质上就是一块内存区;
    • Buffer用于和Channel交互。 从Channel中读取数据到Buffer里,从Buffer把数据写入到Channel;
    • 一个Buffer有三个属性是必须掌握的,分别是:capacity容量、position位置、limit限制。
  • Selectors(选择器)
    • Selector也可以翻译为 多路复用器 。用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。
    • 如此可以实现单线程管理多个Channel,也就是可以管理多个网络链接。
    • 相比使用多个线程,避免了线程上下文切换带来的开销。

参考:
https://www.cnblogs.com/ylspace/p/8128112.html
https://blog.csdn.net/weixin_43896747/article/details/86171681

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值