java中的IO-01

基本介绍

所谓的IO就是Input and Output的意思。在C++中我们学到的IO主要就是两种, Istream and Ostream,cin和cout就是这两个流的对象。C++是这样子的,那么Java又如何呢?我们学习一门语言总是要从hello world开始学起,也就是向控制台中输出hello world这个字符串。在C++中我们使用的是std::cout<<“Hello World”<<std::endl;,在Java中我们使用的是System.out.println("Hello world");。既然是输出,那么肯定是和IO流相关的。coutstd::ostream的一个对象,功能是向控制台输出。那么Java中的这个函数又如何理解呢?System是Java中的一个类。

public final class System extends Object

官方文档中这样说道

  • System类包含几个有用的类字段和方法。它不能被实例化。
  • System类提供的System包括标准输入,标准输出和错误输出流; 访问外部定义的属性和环境变量; 一种加载文件和库的方法; 以及用于快速复制阵列的一部分的实用方法。

我们继续查阅官方文档,发现System类中有几个静态的成员!

static printStream err; // 标准错误输出流
static printStream in; // 标准输入流
static printStream out; // 标准输出流

这样一来Java中的这个输出函数就很好理解了。 System.out是一个对象,相当于C++中的coutprintlnSystem.out的一个static method用于向控制台输出信息,并在最后输出换行。如果不需要换行的话我们可以使用print方法。

OutputStream与InputStream

不过这个System.out为什么能输出字符串呢?那肯定是因为他是一个类似于std::ostream的玩意,他的类型是printStream。我们观察一下它的继承结构。

java.lang.Object 
    java.io.OutputStream 
        java.io.FilterOutputStream 
            java.io.PrintStream 

最顶层的Object这个是毫无疑问的,Java当中的所有的类都是继承于java.lang.Object的。所以说最重要的是第二个 java.io.OutputStream, 正所谓,遇事不决先参看官方文档。

public abstract class OutputStream extends Object implements Closeable, Flushable
  • 这个抽象类是表示字节输出流的所有类的超类。输出流接收输出字节并将其发送到某个接收器。
  • 需要定义OutputStream子类的应用OutputStream必须至少提供一个写入一个字节输出的方法。
void close(); // 关闭此输出流并释放与此流相关联的任何系统资源。 
void flush(); // 刷新此输出流并强制任何缓冲的输出字节被写出。
void write(byte[] b); // 将 b.length字节从指定的字节数组写入此输出流。
void wirte(byte[] b, int off, int len); // 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。
abstract void write(int b); // 将指定的字节写入此输出流。

这个类是一个抽象的类是不能够被实例化的,也就是说他是所有的输出流的一个模子。我们要针对特殊的情形使用这个类的子类。

InputStream和这个也是同样的道理。

FileOutputStream 与 FIleInputStream

前面说了那么多的废话,现在假如我们要输出一些东西到文件中储存能不能做到呢?答案是明显的,我们需要OutputStream的一个合适的子类,这里我们找到了FileOutputStream。我们继续查阅官方文档。

public class FileOutputStream extends OutputStream
  • 文件输出流是用于将数据写入到输出流File或一个FileDescriptor 。文件是否可用或可能被创建取决于底层平台。特别是某些平台允许一次只能打开一个文件来写入一个FileOutputStream (或其他文件写入对象)。在这种情况下,如果所涉及的文件已经打开,则此类中的构造函数将失败。
  • FileOutputStream用于写入诸如图像数据的原始字节流。 对于写入字符流,请考虑使用FileWriter

从文档中我们可以读出,这个类主要是将原始字节流写入到文件当中去的。不过既然这个类可以被实例化,我们就要看看这个类的构造函数是怎么样的。

FileOutputStream(File file) // 创建文件输出流以写入由指定的 File对象表示的文件。  
FileOutputStream(File file, boolean append) // 创建文件输出流以写入由指定的 File对象表示的文件。  
FileOutputStream(FileDescriptor fdObj) // 创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。  
FileOutputStream(String name) // 创建文件输出流以指定的名称写入文件。  
FileOutputStream(String name, boolean append) // 创建文件输出流以指定的名称写入文件。  

这里我们看到了一个File类,可以肯定,这个类代表的就是我们要写入的文件,不过下面的两个构造函数使用的却是String name。这两个是不一样的。

既然是写入,我们还要知道这个类的写入方法是如何的。

void write(byte[] b) // 将 b.length个字节从指定的字节数组写入此文件输出流。  
void write(byte[] b, int off, int len) // 将 len字节从位于偏移量 off的指定字节数组写入此文件输出流。  
void write(int b) // 将指定的字节写入此文件输出流。  

代码测试

public class FileOutputStreamDemo {
    public static void main(String[] args) {
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("sher");
            String content = "sher is a pretty boy!";
            fileOutputStream.write(content.getBytes());
            fileOutputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里我们没有使用FIle类来代表文件,使用的是一个String–“sher”,然后使用write方法将字节写入到文件中。最后使用close方法关闭这个流。这里问题来了,我们只是提供了文件的名字,那么文件放在哪儿呢??莫非是和这个类的文件同级?答案让我很懵逼,这个文件出现在我的项目文件夹的同级。其实这个位置是很沙雕的,因为我有多个项目,谁都不知道这个文件到底是属于哪一个项目的,如果是和src文件夹同级我倒是可以理解。那么我们需要修改文件的位置该如何呢?我们可以修改给定的这个文件的绝对路径,比如说我们是使用如下的代码

FileOutputStream fileOutputStream = new FileOutputStream("D:/sher");

那么这个文件就会创建在D盘下。其实我们没有给他一个绝对路径的时候,他使用的就是相对路径,这里的相对路径不是我们想象中的那个相对与代码文件的,而是相对于整个项目的。比如我们也可以这样写。

FileOutputStream fileOutputStream = new FileOutputStream("./Exercise/src/sher");

点的意思的本目录,点点指的是上一级目录,这里在我看来点是多余的,但是去除点之后就会报错。可能是没有点他就认为这个是一个绝对路径吧。通过上面的方法我们就将文件创建到了src目录下。不过之前我们提到了一个File类,那个类又如何使用呢?

我们可以尝试着使用File类。不过在使用File类之前我们还需要去了解一下File类。

public class File
extends Object
implements Serializable, Comparable<File>

文件和目录路径名的抽象表示,这是官方文档对File类的说明。

File类主要有如下的构战函数

File(File parent, String child) // 从父抽象路径名和子路径名字符串创建新的 File实例。  
File(String pathname) // 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。  
File(String parent, String child) // 从父路径名字符串和子路径名字符串创建新的 File实例。  

我们修改了一下代码

public class FileOutputStreamDemo {
    public static void main(String[] args) {
        try {
            File file = new File("./Exercise/src/sher");
            System.out.println(file.getPath());
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            String content = "sher is a pretty boy!";
            fileOutputStream.write(content.getBytes());
            fileOutputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这个修改倒是不痛不痒的,只是修改了一点点而已。其实File类还有其他非常多的方法,这里我们学习的是IO,只要简单的会使用就好啦。

注意点:

  1. 你可能注意到了FileOutputStream构造函数还有第二个参数(boolean append),默认是false(其实这句话是错误的,Java中的函数没有默认值,只是如果我们没有第二个参数的话,调用的单个参数函数就是相当于第二个参数是false),如果我们将其设置为true的话,写入的文件的时候就不会将原有文件清空,而且接着后面继续写入。(如果文件不存在就会创建该文件)

  2. write(byte[] b, int off, int len)的使用。

    fileOutputStream.write(content.getBytes(), 3, 4);写入文件的就是“r is”从第三个字符开始,写入四个字符。注意一个中文是两个字符。

  3. write(int b)的使用

    这个就更简单了,这个就是写入一个字节。比如wirte(97)就是写入一个字符a.

  4. flush()方法的使用

    有时候写入的时候可能并不会直接写入,我们需要使用flush()方法刷新缓冲区之后才能写入。所有说可以在写入操作完成之后加上这个flush()方法。

上面还只是文件的简单的写入,下面还要学习一下简单的文件的读取操作。

public class FileInputStreamDemo {
    public static void main(String[] args) {
        try {
            FileInputStream fileInputStream = new FileInputStream("./Exercise/src/sher");
            byte[] b = new byte[1024];
            int res = fileInputStream.read(b);
            System.out.println(res);
            System.out.println(new String(b));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

read函数有一个返回值,这个值的意思的读取的字符数。

其实这个FileInputStream和FileOutputStream是非常类似的。只有个别的方法存在差别。

比如说FileInputStream对象没有flush方法,不过他有一个skip方法。

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

如果我们使用skip(1)的话,第一个字节将会被我们跳过,他的返回值是实际跳过的字节数。

总结

到这里我们就基本学完了Java io的几个最基本的类,这些类也是以后学习更加复杂的IO类的基石。


一枚小菜鸡