Java如何进行读取文件(1)—IO流

一、FileInputStream、FileoutputStream

(一)FileInputStream

(1)read()方法

(a)不加改造的read()方法

        FileInputStream fis = null;try {
//             fis = new FileInputStream("C:\\Users\\赵晓东\\Desktop\\javaIO\\FileInputStream.txt");
//             /*相对路径一定是从当前所在的位置作为起点开始找*//*IDEA默认的当前路径是哪里?*/fis = new FileInputStream("abc.txt");int readDate=  fis.read();System.out.println(readDate);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {if(fis !=null){fis.close();}} catch (IOException e) {e.printStackTrace();}}

我的abc文件中写的是abc,读取的结果为:

97

可见它只能读取一个,并且最后一定要在finally里面写一个关闭的方法。避免浪费资源。fis.close();l

(b)加上while循环的read()方法

如果我们想让它一直读取怎么办呢?这时候就要用到了循环。

        FileInputStream fis = null;try {fis = new FileInputStream("abc.txt");int readCount = 0;while (readCount !=-1){readCount =fis.read();if(readCount ==-1){break;}System.out.println(readCount);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {if(fis!=null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}}

解释:当它读到最后会返回一个-1代表读完了。

(c)读取多个的read(byte[] b)

  • int
  • read(byte[] b)
  • 从该输入流读取最多 b.length个字节的数据为字节数组。

 这样可以一次最多读取b.length个字节,减少了硬盘和内存的交互,提高程序的执行效率,往byte[]数组当中读。

首先,我在zxd文件中写了abcdef,int readCount表示返回的是读取的数量。

        FileInputStream fis = null;try {fis = new FileInputStream("zxd");/*第一次读*/byte[] bytes = new byte[4];int readCount = fis.read(bytes);System.out.println(readCount);/*第二次读*/readCount = fis.read(bytes);System.out.println(readCount);/*第三次读*/readCount = fis.read(bytes);System.out.println(readCount);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {if(fis!=null){fis.close();}} catch (IOException e) {e.printStackTrace();}}

读取的结果

4
2
-1

并且在第二次读的时候,会把前面ab会覆盖了。下面我们用图来模拟一下。

第一次读的时候,会把abcdef中的abcd读入到数组里面。

第二次读的时候,ef会把前面ab覆盖了。 

(2)available()

这个表示返回流当中剩余的没有读到的字节数量

    • intavailable()

      返回从此输入流中可以读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。

 

            FileInputStream fis = null;fis = new FileInputStream("zxd");int allNumber=fis.available();System.out.println(allNumber);
6

可见available返回的是文件的大小。正好可以通过它,传入到数组中然后这样就可以直接读取了。

            FileInputStream fis = null;fis = new FileInputStream("zxd");int allNumber=fis.available();System.out.println(allNumber);byte [] bytes = new byte[allNumber];int number =fis.read(bytes);System.out.println(new String(bytes));
6
abcdef

这样就可以通过avaliable方法获得文件大小,然后再用数组进行声明,直接用read方法进行读取,就不会浪费,也不会因为空间太小了。

(3)skip跳过几个字节不读取

  • long
  • skip(long n)
  • 跳过并从输入流中丢弃 n字节的数据。

 

            FileInputStream fis = null;fis = new FileInputStream("zxd");fis.skip(3);System.out.println(fis.read());

跳过3个正好读取的是d,也就是100了。

100

(二)FileoutputStream

(1)Wirte()方法进行写

    • voidwrite(byte[] b)

      b.length个字节从指定的字节数组写入此文件输出流。

    • voidwrite(byte[] b, int off, int len)

      len字节从位于偏移量 off的指定字节数组写入此文件输出流。

也可以全写进去,也可以只写一部分。

      FileOutputStream fot = null;try {fot = new FileOutputStream("myfile");byte [] bytes = {97,98,99,100};fot.write(bytes);/*一定要进行刷新*/fot.flush();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {fot.close();} catch (IOException e) {e.printStackTrace();}}

 这时候,我们会看见idea里面多了一个myfile文件,里面还有abcd

如果没有myfile文件会新建,如果有则直接输入。输入的时候会把源文件情况,再输入。所以谨慎使用。

下面这种方式是以追加的方式在末尾写入,不会情况源文件内容。

    • FileOutputStream(String name, boolean append)

      创建文件输出流以指定的名称写入文件。

 这时候我们会发现myfile文件中追加了abcd

 将字符串转换成数组的形式进行追加

 

三、FileReader、FileWriter

 文件字符输入流,只能读取普通文本,读取文本内容时,比较方便,快捷。这次就变成了char[]数组了。他们两个的方法和FileInputStream和FinleoutputStream一样。

四、带有缓冲区的字符流(这时候就不需要char byte数组了,自带有缓冲区)

(一)BufferedReader

带有缓冲区的字符输入流。使用这个流的时候,不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。

从构造方法可以看出,如果要实例一个BufferedReader,里面需要传入一个流。

        FileReader reader = new FileReader("zxd");/**/BufferedReader br = new BufferedReader(reader);/*关闭流*/br.close();

当一个流的构造方法中,需要一个流的时候,被传进来的流叫做节点流。外部负责包装的流,叫做:包装流。还有一个名字叫做:处理流。

从源代码可以看出,当我们关闭流的时候,只需要关闭最后一个即可。

 bufferedReader.readLine()能读一行。但是不带换行符。

 (a)如果字节流想传进入怎么办?

这时候需要通过InputStreamReader

 这时候报错,的原因是字节流不能作为BufferedReader的参数,所以这时候需要InputStreamReader

(1) readline()

        FileReader reader = new FileReader("zxd");/**/BufferedReader br = new BufferedReader(reader);String s1 =br.readLine();System.out.println(s1);/*关闭流*/br.close();
abcdef

readline()读取直接读取一行。

接下来,我们可以对它进行循环。

        String s = null;while ((s=br.readLine())!=null){System.out.println(s);}

但是readline()不带换行符。

(2)当我们想往BufferedReader中传入字节流可以吗?(FileInputStream)

可以看出,是不可以的。 

这个时候需要使用转换流了。(将字节流转换成字符流)

java.io.InputStreamReader
java.io.outputStreamWriter
    • InputStreamReader(InputStream in)

      创建一个使用默认字符集的InputStreamReader。

通过 构造方法可以看出InputStreamReader里面可以传入InputStream的。

 这个时候就不报错了。

(二)BufferedWriter

        FileWriter f1 = new FileWriter("zjy");BufferedWriter br = new BufferedWriter(f1);/*进行文件的写工作*/br.write("hahah");/*刷新*/br.flush();/*关闭包装流/*/br.close();

当然,你想追加,可以构造方法哪里写true。同样,如果想用字节流(Inputoutstream)也是需要转换流的。(outputStreamReader)

五、PrintStream标准的字节输出流(默认输出到控制台)

我们平时输出的:

System.out.println("Hello World");

其实,就是这个字节输出流

PrintStream ps = System.out
ps.println("Hello World");

当然它不需要关闭close().

那么可以输出改变方向吗?

            /*指向一个日志文件*/PrintStream out = null;try {out = new PrintStream(new FileOutputStream("log.txt",true));/*改变输出方向*/System.setOut(out);/*日期当前时间*/Date nowTime = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");String strTime = sdf.format(nowTime);System.out.println(strTime +":"+msg);} catch (FileNotFoundException e) {e.printStackTrace();}
public class LogTest {public static void main(String[] args) {/*测试工具类*/Logger.log("调用gc,启动垃圾回收");Logger.log("用户尝试进行登录");}
}

可以,以上就是日志工具的实现原理。

六、对象专属的流ObjectInputStream、ObjectOutputStream

(1)序列化ObjectOutputStream

对象的序列化(Serialize)和反序列化(DeSerialize)

将内存中的Java对象放到硬盘文件上叫序列化,是因为内存中的Java对象切割成一块一块的并且编号,放到硬盘文件上。

 如果从硬盘文件恢复到内存中,那么叫反序列化。

一个是拆分一个是组装。 

如果需要序列化就是需要ObjectOutputStream了。如果反序列化就是需要ObjectInputStream了。

下面用个例子说明一下:

首先写一个Student类

public class Student {private  int no;private  String name;public Student(int no, String name) {this.no = no;this.name = name;}public Student(int no) {this.no = no;}public Student(String name) {this.name = name;}public Student() {}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
    public static void main(String[] args) throws  Exception{/*进行序列化例子*/Student s1 = new Student(1,"赵晓东");/*通过流去给一个文件students为文件的名字*/FileOutputStream f1 = new FileOutputStream("Students");/*调用ObjectOutputStream*/ObjectOutputStream o1 = new ObjectOutputStream(f1);/*调用方法进行序列化*/o1.writeObject(s1);/*刷新*/o1.flush();/*关闭流*/o1.close();}
}
Exception in thread "main" java.io.NotSerializableException: com.javase.IO.Studentat java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)at com.javase.IO.ObjectOutputStreamTest.main(ObjectOutputStreamTest.java:16)

这时候我们看见会报错。这个异常的意思是Student不支持序列化。然后我们让student去实现Serializable

这时候会有Students文件,并且是乱码的形式

 

所以参与序列化和反序列化的对象必须实现Serializable接口 

这个接口当中什么都没有,那么它起到什么作用呢?起到了标识的作用,JVM看见了它会特殊待遇。JAVA中有两种接口,一种是普通接口,另一种是标志接口,标识接口是给JVM虚拟机看的。

JVM看到Serializable会自动生成一个序列化版本号。

(2)反序列化ObjectInputStream

    public static void main(String[] args) throws  Exception{/*创建流读取文件*/FileInputStream f1 = new FileInputStream("Students");/*进行反序列化*/ObjectInputStream o1 = new ObjectInputStream(f1);/*调用方法*/Object ob =o1.readObject();System.out.println(ob);o1.close();}
}

(3)能不能一次序列化多个对象

可以,可以将序列化放到集合当中,序列化集合。

User类

public class User implements Serializable {private  int id;private  String name;public User(int id, String name) {this.id = id;this.name = name;}public User() {}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}
}

序列化

    public static void main(String[] args)  throws  Exception{/*创建集合*/List l1 = new ArrayList<>();l1.add(new User(1,"哈哈哈"));l1.add(new User(2,"呵呵呵"));l1.add(new User(3,"嘿嘿嘿"));
//        通过流去读取FileOutputStream f1 = new FileOutputStream("Users");
//        序列化ObjectOutputStream o1 = new ObjectOutputStream(f1);
//        调用序列化方法o1.writeObject(l1);o1.flush();o1.close();}

结果

(4)transient表示不参加序列化操作

private transient String name;

(5)关于序列化版本号

Java虚拟机看到Serializable接口之后,会自动生成了一个序列化版本号,这里没有手动手写处理,java虚拟机会默认提供这个序列化版本号。

java语言中采用什么机制来区分类的?

第一:首先通过类名进行区分,如果类名不一样,肯定不是一个类。

第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。

这种自动生成序列化版本号有什么缺陷?

一旦自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类。

最终结论:

凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号,这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。

所以建议将序列化版本号手动的写出来,不建议自动生成。

private static final long serialVersionUID=1L;

 总结:记得在一级的时候,不知道序列化是干什么的,不知道怎么去读取学生的文件的,现在终于是明白了。另外我发现流在读取的时候和集合的遍历很相似。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部