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)使用字节流读写文本文件
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类型
-
使用字节流写二进制文件
-
利用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提供的两个标准输入/输出流,主要用于从键盘接受数据以及向屏幕输出数据
-
System.in常见方法如下所示
int read(),此方法从键盘接收一个字节的数据,返回值是该字符的ASCII码
int read(byte[]buf),此方法从键盘接收多个字节的数据,保存至buf中,返回值是接收字节的个数,不是ASCII码
-
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()方法写对象,也就是输出可序列化对象
反序列化获取对象信息
反序列化,顾名思义就是于序列化相反,序列化是将对象的状态信息保存存储介质中,反序列化则是从特定存储介质中读取数据并重新构建成对象的过程。通过反序列化,可以将存储在文件上的对象信息读取出来,然后重新构建为对象,这样就不需要再将文件上的信息一一读取、分析再组织为对象
-
反序列化的步骤大致概括为以下两步:
创建一个对象输入流(ObjectInputStream),它可以包装一个其它类型的输入流,如文件输入流FileInputStream
通过对象输入流的readObject()方法读取对象,如果程序知道该Java对象类型,则可以将该对象强制转换成其真实的类型
注意:如果文件中使用序列化机制写入多个对象,那么反序列化恢复对象时,必须按照写入的顺序读取。如果一个可序列化的类,有多个父类(包括直接或间接父类),则这些父类要么时可序列化的,要么有无参的构造器;否则会抛出异常
出于对安全的考虑,某些属性应限制被序列化。解决的办法时使用transient修饰
对象引用的序列化
- 如果一个类的成员包含其他类的对象,如班级中包含学生类型的对象,那么当要序列化班级对象时,则必须保证班级类和学生类都是可序列化的。即当需要序列化某个特定对象时,它的各个成员对象也必须是可序列化的。
- 序列化的算法规则如下
- 所有保存到磁盘中的对象都有一个序列号
- 当程序试图序列化一个对象时,将会检查是否已经被序列化,只有序列化后的对象才能被转换成字节序列输出
- 如果对象已经被序列化,则程序直接输出一个序列化编号而不再重新序列化
使用反射机制
Java的反射机制是Java的特性之一,反射机制是构建框架技术的基础所在
Java反射机制是指在运行状态中,动态获取信息以及动态调用对象方法的功能
-
Java反射的三个动态性质
- 运行时生成对象实例
- 运行期间调用方法
- 运行时更改属性
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进行操作
反射的应用
-
获取类的信息
- 通过反射获取类的信息分为两步,首先获取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对象
-
-
从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对象直接访问并修改对象的属性值
- 创建对象
- 通过反射来创建对象有如下两种方式
- 使用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。
-
访问类的属性
- 使用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)
-
访问类的方法
-
使用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语言访问权限检查
-
使用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对象,从配置文件读取的只是某个类的字符串类名,程序需要根据字符串来创建对应的实例,就必须使用反射
在实际开发中,没有必要使用反射来访问已知类的方法和属性,只有当程序需要动态创建某个类的对象的时候才会考虑使用。例如,从配置文件中读取以字符串形式表示的类时,就要使用反射来获取它的方法和属性
-
-
-