IO

Java 使用IO操作文件

  • Java.io包提供了一些接口和类,对文件进行基本的操作,包括对文件和目录属性的操作、对文件读写的操作等

    [图片上传失败...(image-d92d05-1570854191853)]

  • File对象既可以表示文件,也可以表示目录,在程序中一个File对象可以代表一个文件或目录。利用它可用来对文件或目录进行基本操作。它可以查出与文件相关的信息,如名称、最后修改日期、文件大小等;

认识Java流

  • 读文件是指把文件中的数据读取到内存当中。反之,写文件就是把内存中的数据写到文件中Java中通过来进行上述操作

  • 流是指一连串流动的字符,是以先进先出的方式发送和接受数据的通道

  • 流分为输入流和输出流。输入/输出流是相对于计算机内存来说的,如果数据输入到内存,则称之为输入流,如果从内存中输出则称为输出流。Java的输出流主要有OutputStream和Writer作为基类,而输入流则主要由InputStream和Reader作为基类

  • java.io包中,封装了许多输入/输出流的API。在程序中,这些输入/输出流类的对象称为流对象。可以通过这些流对象将内存中的数据以流的方式写入文件,也可以将文件中的数据以流的方式读取到内存。

  • 构造流对象时往往会和数据源联系起来。数据源分为源数据源和目标数据源。输入流关联的时源数据源;输出流关联的时目标数据源

  • 输入/输出流又分为字节流和字符流

  • 字节流时8位通用字节流,其基本单位是字节。字节流的基类是InputStream类和OutputStream类,它们是抽象类。

  • 字符流是16位Unicode字符流,基本单位是Unicode字符。字符流最适合用来处理字符串和文本,因为它们支持国际上大多数的字符集和语言。字符流的基类是Reader类和Writer类,它们也是抽象类。一个被访问的流基本特征都是通过实现这四个抽象类的方法来建立。

  1. 读写文本文件

    (1)使用字节流读写文本文件

    FileInputStream称为文件输入流,它是字节输入流InputStream抽象类的一个子类,它的作用是将文件中的数据输入到内存,可以利用它来读取文本文件中的数据

//构造一个文件输入流对象 InputStream fileObject=new FileInputStream("text.txt") //此时的文件输入流对象fileObject就和源数据源(text.txt)关联起来。 //利用文件输入流类的方法读取文本文件的数据。 fileObject.available(); //可读取的字节数 fileObject.read(); //读取文件的数据


(2)使用字节流类FileOutputStream写文本文件

FileOutputStream称为文件输出流,它是字节输出流OutputStream抽象类的子类,它的作用是把内存中的数据输出到文件中,可以利用它把内存中的数据写入到文本文件中

//构造一个文件输出流对象 OutputStream fos = new FileOutputStream("text.txt"); //此时的文件输出流对象fos就和目标源数据源("text.txt"文件)关联起来了

//利用文件输出流的方法把数据写入到文本文件中。 String str="好好学习Java" byte[]words=str.getBytes(); //利用write方法将数据写入到文件中去 fos.write(words,0,words.length)


[关于IO流的相关知识](https://www.jianshu.com/p/1c342147d428)

*注意:在创建FileOutputStream实例时,如果相应的文件不存在,就会自动创建一个空的文件*

*如果参数file或name表示的文件路径存在,但是代表一个文件目录,则此时会抛出FIleNotFoundException类型的异常*

*默认情况下,向文件写数据时将覆盖文件中原有的内容*

2\. 使用字符流读写文本文件

(1).使用字符流类BufferedReader和FileReader读文本文件

+ BufferedReader和FileReader两个类都是Reader抽象类的子类,它们可以通过字符流的方式读取文件,并使用缓冲区,提高了读文本文件的效率

  ```Java
  //创建一个BufferedReader对象。
  FileReader fr=new FileReader("mytest.txt");
  BufferedReader br=BufferedReader(fr);
  //利用BufferedReader类的方法读取文本文件的数据。
  br.readLine();//读取一行数据,返回字符串

​ (2).使用字符流类BufferedWriter和FileWriter写文本文件

    • BufferedWriter和FileWriter都是字符输出流Writer抽象类的子类,它们可以通过字符流的方式并通过缓冲区把数据写入文本文件,提高了写文本文件的效率
//构造一个BufferedWriter对象
FileWriter fw=new FileWriter("mytest.txt");
BufferedWriter bw=new BufferedWriter(fw);
//利用BufferedWriter类的方法写文本文件
bw.writer("hello");

读写二进制文件

  • 读写二进制文件的常用类有DateInputStream和DateOutputStream。

1. 使用字节流读二进制文件

  • 利用DateInputStream类读二进制文件,其实与利用FileInputStream类读文本文件极其相似,也要用到FIleInputStream类关联二进制文件
//构造一个数据输入流对象
FileInputStream fis=new FileInputStream("HelloWorld.class");
DateInputStream dis=new DateInputStream(fis);
//利用数据输入流类的方法读取二进制文件中的数据
dis.readInt();    //读取出来的是整数
dis.readByte();   //读取出来的数据是Byte类型

  1. 使用字节流写二进制文件

    • 利用DateOutputStream类写二进制文件,其实与利用FileOutputStream类写文本文件极其相似,也要用到FileOutputStream类关联二进制文件

      //构造一个数据输出流对象。
      FileOutputStream outFile=new FileOutputStream("temp.class");
      DataOutputStream out=new DataOutputStream(outFile);
      //利用数据输出流类的方法把数据写入二进制文件 
      out.write();//把数据写入二进制文件
      
      
  • DataInputStream类与DataOutpuStream类搭配使用,可以按照与平台无关的方式从流中读取基本数据类型的数据,如int、folat、long 、double 和boolean。此外DataInputStream的readUTF()方法能读取采用UTF-8字符集编码的字符串。

  • DataOutputStream类可以按照与平台无关的方式从流中读取基本数据类型的数据,如int、folat、long 、double 和boolean。此外DataInputStream的readUTF()方法能读取采用UTF-8字符集编码的字符串。

  • DataOutputStream类的所有写方法都是以write开头,如writeByte ()、writeLong()等方法。

重定向标准I/O

  • 对于文件操作,还有两个非常重要的流,即System.in和System.out,它们是Java提供的两个标准输入/输出流,主要用于从键盘接受数据以及向屏幕输出数据

    1. System.in常见方法如下所示

      • int read(),此方法从键盘接收一个字节的数据,返回值是该字符的ASCII码

      • int read(byte[]buf),此方法从键盘接收多个字节的数据,保存至buf中,返回值是接收字节的个数,不是ASCII码

      1. System.out常见方法如下

        • print(),向屏幕输出数据,不换行,参数可以是Java的任意数据类型。

        • println(),向屏幕输出数据,换行,参数可以是Java的任意数据类型。

认识序列化

  • 序列化就是将对象的状态存储到特定存储介质中的过程,也就是将对象状态转换为可保持或可传输格式的过程。在序列化过程中,会将对象的共有成员、私有成员包括类名,转换为字节流,然后在把字节流写入数据流,存储到存储介质中,这里说的存储介质通常指的是文件。
  • 使用序列化的意义在于将Java对象序列化后,可以将其转换为字节序列,这些字节序列可以被保存在磁盘上,也可以借助网络进行传输,同时序列化后的对象保存的是二进制撞他其,这样实现了平台无关性

序列化保存对象信息

  • 序列化机制允许将实现了序列化的Java对象转换为字节序列,这个过程需要借助于I/o流来实现。

  • Java中只有实现了java.io.Serialiazable接口的类的对象才能被序列化,Serialiazable表示可串行的、可序列化的,所以,对象序列化在某些文献上也被称为串行化

    (1). 创建一个对象输出流(ObjectOutputStream),它可以包装一个其它类型的输出流,如文件输出流FileOutputStream

    ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("C:/myDoc/stu.txt"))
    
    
  • 通过对象输出流的writerObject()方法写对象,也就是输出可序列化对象

反序列化获取对象信息

  • 反序列化,顾名思义就是于序列化相反,序列化是将对象的状态信息保存存储介质中,反序列化则是从特定存储介质中读取数据并重新构建成对象的过程。通过反序列化,可以将存储在文件上的对象信息读取出来,然后重新构建为对象,这样就不需要再将文件上的信息一一读取、分析再组织为对象

  • 反序列化的步骤大致概括为以下两步:

    1. 创建一个对象输入流(ObjectInputStream),它可以包装一个其它类型的输入流,如文件输入流FileInputStream

    2. 通过对象输入流的readObject()方法读取对象,如果程序知道该Java对象类型,则可以将该对象强制转换成其真实的类型

注意:如果文件中使用序列化机制写入多个对象,那么反序列化恢复对象时,必须按照写入的顺序读取。如果一个可序列化的类,有多个父类(包括直接或间接父类),则这些父类要么时可序列化的,要么有无参的构造器;否则会抛出异常

出于对安全的考虑,某些属性应限制被序列化。解决的办法时使用transient修饰

对象引用的序列化

  • 如果一个类的成员包含其他类的对象,如班级中包含学生类型的对象,那么当要序列化班级对象时,则必须保证班级类和学生类都是可序列化的。即当需要序列化某个特定对象时,它的各个成员对象也必须是可序列化的。
  • 序列化的算法规则如下
    • 所有保存到磁盘中的对象都有一个序列号
    • 当程序试图序列化一个对象时,将会检查是否已经被序列化,只有序列化后的对象才能被转换成字节序列输出
    • 如果对象已经被序列化,则程序直接输出一个序列化编号而不再重新序列化

使用反射机制

  • Java的反射机制是Java的特性之一,反射机制是构建框架技术的基础所在

  • Java反射机制是指在运行状态中,动态获取信息以及动态调用对象方法的功能

  • Java反射的三个动态性质

    • 运行时生成对象实例
    • 运行期间调用方法
    • 运行时更改属性
    img
  • Java反射机制在编译时并不确定是哪个类被加载了,而是在程序运行时才加载、探知、使用。这样的特点就是反射。这类似于光学中的反射概念。所以把Java的这种机制称为反射机制。在计算机科学领域,反射是指一类应用,它们能够自描述和自控制

  • Java反射机制能够知道类的基本结构,这种对Java类结构探知的能力,称为Java类的自审。在使用MyEclipse时,Java代码的自动提示功能就是利用了Java的反射原理,是对所创建对象的探知和自审

  • 通过Java反射,可以实现以下功能

    • 在运行时判断任意一个对象所属的类

    • 在运行时构造任意一个类的对象

    • 在运行时判断任意一个类所具有的方法和属性

    • 在运行时任意调用一个对象的方法

  • Java反射常用API

    • Class:反射的核心类,反射所有的操作都是围绕该类来生成的。通过Class类,可以获取类的属性、方法等内容信息
    • Field类:表示类的属性,可以获取和设置类中属性的值
    • Method类:表示类的方法,可以用来获取类中方法的信息,或执行方法
    • Constructor:表示类的构造方法
  • Java程序中使用反射的基本步骤如下:

    • 导入java.lang.reflect.*

    • 获得需要操作的类的Java.lang.Class对象

    • 调用Class的方法获取FIeld、Method等对象

    • 使用反射API进行操作

反射的应用

  1. 获取类的信息

    • 通过反射获取类的信息分为两步,首先获取Class对象,然后通过Class对象获取信息
  • (1)获取Class对象

    • 每个类被加载后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问Java虚拟机中的这个类。获得Class对象通常有如下3中方式:

      • 调用对象的getClass()方法:getClass()方法是java.lang.Object类中的一个方法,所有的Java对象都可以调用该方法,该方法会返回该对象所属类的Class对象。

        Student p= new Student();//Student为自定义的学生类型
        Class cla=p.getCLass();//cla为Class对象
        
        
  • (2)调用类的Class属性

    • 调用某个类的class属性可获取该类对应的Class对象,这种方式需要在编译期间就知道类的名称

      Class cla=Student.class;//Student为自定义的学生类型
      
      
    • 上述代码中,Student.class将会返回Student类对应的Class对象

  • (3) 使用Class类的forName()静态方法

    • 使用Class类的forName()静态方法也可以获取该类对应的Class对象。该方法需要传入字符串参数,该字符串参数的值是某个类的全名,即要在类名前添加完整的包名

      Class cla=Class.forName("com.pb.jadv.refletion.Student");//正确
      Class cla=Class.forName("Student");//错误
      
      

      后两种方式都是直接根据类来获取该类的Class对象,相比之下调用某个类的Class属性来获取该类对应Class对象这种方式更有优势。

      好处:1.代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在。

      2.程序性能更高,因为这种方式无需调用方法,所以性能更好

      因此,大部分时候都应该使用调用某个类的class属性的方式来获取指定类的Class对象

  1. 从Class对象获取信息

    • 在获得了某个类所对应的Class对象之后,程序就可以调用Class对象的方法来获取该类的详细信息
  • (1)访问Class对应的类所包含的构造方法

  • (2)访问Class对应的类所包含的方法

  • (3) 访问Class对应的类所包含的属性

  • (4) 访问Class对应的类所包含的注解

  • (5)访问Class对应的类的其他信息

    上述几个 步骤都有相关的常用方法此处省略不写

    Class对象可以获得该类里的成员,包括方法、构造方法及属性。其中方法由Method对象表示,构造方法由Constructor对象表示,属性由Field对象表示。

    Method、Constructor、Filed这3个类都定义在java.lang.reflect包下,并实现了java.lang.reflect.Member接口,程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用相应的构造方法创建对象,通过Field对象直接访问并修改对象的属性值

  1. 创建对象
    • 通过反射来创建对象有如下两种方式
      • 使用Class对象newInstance()方法创建对象
      • 使用Constructor对象创建对象

使用Class对象的newInstance()方法来创建Class对象对应类的实例,这种方式要求Class对象的对应类有默认构造方法,而执行newInstance()方法时实际上是利用默认构造方法来创建该类的实例。而使用Constructor对象创建对象,要先使用Class对象获取指定的Constructor,再调用Constructor对象的newInstace()方法创建该Class对象对应类的实例。通过这种方式可以选择使用某个类的制定构造方法来创建实例

//使用newIstance()方法创建对象
improt java.util.Date;
public class test{
    public static void main(String[]args)throws Exception{
    Class cla=Date.class;
    Date d=(Date)cla.newInstance();
    System.out.println(d.toString);
    }
}

如果创建Java对象时不是利用默认构造方法,而是使用指定的构造方法,则可以利用Constructor对象,每个Constructor对应一个构造方法。指定构造方法创建Java对象需要如下3个步骤

  • (1)获取该类的Class对象
  • (2)利用Class对象的getConstructor()方法来获取指定构造方法
  • (3)调用Constructor的newInstance()方法创建Java对象
//利用Constructor对象指定的构造方法创建对象
import java.lang.reflect.Constructor;
import java.util.Date;

public class Test{
    public static void main(String[]args)throws Exception{
    //获取Date对应的Class对象
    Class cla = Date.Class;
    //获取Date中带一个长整型参数的构造方法
    Constructor cu=cla.getConstructor(long.class);
    //调用Constructor的newInstance()方法创建对象
    Date d=(Date)cu.newInstance(1987);
    System.out.println(d.toString);
    }
}

上述代码中,要使用Date类中带一个long类型参数的构造方法,首先要在获取Constructor时指定参数值为long.class,然后在使用newInstace()方法时传递一个实际值1987。

  1. 访问类的属性

    • 使用Field对象可以获取对象的属性。通过Field对象可以对属性进行取值或赋值操作
import java.lang.reflect.*
/*自定义学生类*/
class Student{
    private String name;
    private int age;
    public String toString(){
    return"name is"+name+",age is"+age;
    }
}

/*测试类*/
public class Test{
    public static void main(String[]args)throws Exception{
    //创建一个Student对象
    Student p=new Student();
    //获取Students对应的Class对象
    Class cla=Student.class;

    //获取Students类的name属性,使用getDeclaredField()方法可获取各种访问级别的属性
    Field nameFiled=cla.getDeclaredField("name");
    //设置通过反射访问该Field时取消全限检查
    nameField.setAccessible(true);
    //调用set()方法为p对象的指定Field设置值
    nameField.set(p,"Jack");

    //获取Students类的age属性,使用getDeclaredField()方法可获取各种访问级别的属性
    Field ageField=cla.getDeclaredField("age");
    //设置通过反射访问该Field时取消权限检查
    ageField.setAccessible(true);
    //调用setInt()方法为p对象的指定Field设置值
    ageField.setInt(p,20);
    System.out.println(p);
    }
}

通常情况下,Student类的私有属性name和age只能再Student里访问,但示例代码通过反射修改了Student对象的name和age属性值。在这里,并没有用getField()方法来获取属性,因为getField方法只能获取public访问权限的属性,而使用getDeclaredField()方法则可以获取所有访问权限的属性。

另外,为name和age赋值的方式不同,为name赋值只用了set()方法,而为age赋值则使用了setInt()方法,因为前者时引用类型(String),后者为值类型(int)

  1. 访问类的方法

    • 使用Method对象可以调用对象的方法。在Method类中包含一个invoke()方法,方法定义如下

      Object invoke(Object,Object args)
      
      

      其中,obj是执行该方法的对象,args是执行该方法时传入该方法的参数

      //通过反射调用Student类的方法
      import java.lang.reflect.*;
      class Student{
          private String name;   //姓名
          private int age;        //年龄
          public String getName(){
          return name;
          }
          public void setName(String name){
          this name=name;
          }
          public int getAge(){
          return age;
          }
          public void setAge(int age){
          this.age=age;
          }
          public String toString(){
          return "name is"+name+",age is"+age;
          }
      }
      
      public class Test{
          public static void main(String[]args)throws Exception{
          //获取Student对应的Class对象
          Class cla=Student.class;
          //创建Studendt对象
          Student p= new Student();
          //得到setName方法
          Method met1=cla.getMethod("setName",String.class);
          //调用setName,为name赋值
          met1.invoke(p,null);
      
          //得到getName方法
          Method met=cla.getMethod("getName",null);
          //调用getNmae,获取name 的值
          Object o=met.invoke(p,null);
          System.out.println(o);
          }
      }
      
      

      如果把Student类的setName()方法的访问全限设为私有再运行程序,则会抛出NoSuchMethodException异常。这是因为当通过Method的invoke()方法调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,可以先调用setAccessible()方法,将Method对象的accessible标志设置为布尔值,值为true则表示给Method在使用时应该取消Java语言访问权限检查;值为false则表示该Method在使用时应该进行Java语言访问权限检查

      1. 使用Array类动态创建和访问数组

        • 在java.lang.reflect包下还提供了一个Array类,此Array类的对象可以代表所有数组。程序可以通过使用Array类来动态地创建数组、操作数组元素等

          /*创建数组arr,并为元素赋值*/
          //创建一个元素类型为String,长度为10的数组
          Object arr=Array.newInstance(String.class,10);
          //依次为arr数组中index为5,6的元素赋值
          Array.set(arr,5,"Jack");
          Array.set(arr,6,"John");
          //一次取出arr数组中index为5,6的元素的值
          Object o1=Array.get(arr,5);
          Object o2=Array.get(arr,6);
          
          

          使用Array类动态地创建和操作数组很方便,大大简化了代码。关于Array类更多的方法可以在使用时查看API

          注意:使用反射虽然会在很大程度上提高代码的灵活性,但是不能滥用反射,因为通过反射创建对象时性能要稍微低一些。实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。因为在很多Java框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据字符串来创建对应的实例,就必须使用反射

          在实际开发中,没有必要使用反射来访问已知类的方法和属性,只有当程序需要动态创建某个类的对象的时候才会考虑使用。例如,从配置文件中读取以字符串形式表示的类时,就要使用反射来获取它的方法和属性

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容