概述
java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。
Java 的 I/O 大概可以分成以下几类:
- 磁盘操作:File
- 字节操作:InputStream 和 OutputStream
- 字符操作:Reader 和 Writer
- 对象操作:Serializable
- 网络操作:Socket
- 新的输入/输出:NIO
File
Java中IO操作有相应步骤,以文件操作为例,主要操作流程如下:
1.使用File类打开一个文件
2.通过字节流或字符流的子类,指定输出的位置
3.进行读/写操作
4.关闭输入/输出
那么我们先来介绍一下File类
Java文件类在java.io包中,它以抽象的方式代表文件名和目录路径名。该类主要用于获取文件和目录的属性,文件和目录的创建、查找、删除、重命名等,但不能进行文件的读写操作。
File对象代表磁盘中实际存在的文件和目录。通过以下构造方法创建一个File对象。
通过给定的父抽象路径名和子路径名字符串创建一个新的File实例。File(File parent, String child)
通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例。File(String pathname)
根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。File(String parent, String child)
通过将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例。File(URI uri)
注意:
1.在各个操作系统中,路径的分隔符是不一样的,例如:Windows中使用反斜杠:"\
",Linux|Unix中使用正斜杠:"/
"。在使用反斜杠时要写成"\\
"的形式,因为反斜杠要进行转义。如果要让Java保持可移植性,应该使用File类的静态常量File.pathSeparator。
2.构建一个File实例并不会在机器上创建一个文件。不管文件是否存在,都可以创建任意文件名的File实例。可以调用File实例上的exists()方法来判断这个文件是否存在。通过后续的学习我们会知道,当把一个输出流绑定到一个不存在的File实例上时,会自动在机器上创建该文件,如果文件已经存在,把输出流绑定到该文件上则会覆盖该文件,但这些都不是在创建File实例时进行的。
创建File对象成功后,可以使用以下列表中的方法操作文件。
File1.png
File2.png
File3.png
File4.png
下面给出一个使用File类的实例:
import java.io.File;
public class DirList {
public static void main(String args[]) {
String dirname = "/java";
File f1 = new File(dirname);
if (f1.isDirectory()) {
System.out.println( "Directory of " + dirname);
String s[] = f1.list();
for (int i=0; i < s.length; i++) {
File f = new File(dirname + "/" + s[i]);
if (f.isDirectory()) {
System.out.println(s[i] + " is a directory");
} else {
System.out.println(s[i] + " is a file");
}
}
} else {
System.out.println(dirname + " is not a directory");
}
}
}
小贴士:lastModified()方法返回的是从时间戳(1970年1月1日0时0分0秒)到当前的毫秒数,返回值类型是long,可以用Date类对它进行包装使其更易读。
Java中的目录
创建目录:
File类中有两个方法可以用来创建文件夹:
- mkdir( )方法创建一个文件夹,成功则返回true,失败则返回false。失败表明File对象指定的路径已经存在,或者由于整个路径还不存在,该文件夹不能被创建。
- mkdirs()方法创建一个文件夹和它的所有父文件夹。
下面的例子创建 "/tmp/user/java/bin"文件夹:
import java.io.File;
public class CreateDir {
public static void main(String args[]) {
String dirname = "/tmp/user/java/bin";
File d = new File(dirname);
// 现在创建目录
d.mkdirs();
}
}
mkdirs是递归创建文件夹,允许在创建某文件夹时其父文件夹不存在,从而一同创建;mkdir必须满足路径上的父文件夹全都存在
注意: Java 在 UNIX 和 Windows 自动按约定分辨文件路径分隔符。如果你在 Windows 版本的 Java 中使用分隔符 (/) ,路径依然能够被正确解析。
读取目录:
一个目录其实就是一个 File 对象,它包含其他文件和文件夹。
如果创建一个 File 对象并且它是一个目录,那么调用 isDirectory() 方法会返回 true。
可以通过调用该对象上的 list() 方法,来提取它包含的文件和文件夹的列表。
下面展示的例子说明如何使用 list() 方法来检查一个文件夹中包含的内容:
import java.io.File;
public class DirList {
public static void main(String args[]) {
String dirname = "/tmp";
File f1 = new File(dirname);
if (f1.isDirectory()) {
System.out.println( "目录 " + dirname);
String s[] = f1.list();
for (int i=0; i < s.length; i++) {
File f = new File(dirname + "/" + s[i]);
if (f.isDirectory()) {
System.out.println(s[i] + " 是一个目录");
} else {
System.out.println(s[i] + " 是一个文件");
}
}
} else {
System.out.println(dirname + " 不是一个目录");
}
}
}
删除目录或文件:
删除文件可以使用 java.io.File.delete() 方法。
以下代码会删除目录/tmp/java/,即便目录不为空。
测试目录结构:
/tmp/java/
|-- 1.log
|-- test
deleteFolder是一个递归函数,类似于DFS思想
import java.io.File;
public class DeleteFileDemo {
public static void main(String args[]) {
// 这里修改为自己的测试目录
File folder = new File("/tmp/java/");
deleteFolder(folder);
}
//删除文件及目录
public static void deleteFolder(File folder) {
File[] files = folder.listFiles();
if(files!=null) {
for(File f: files) {
if(f.isDirectory()) {
deleteFolder(f);
} else {
f.delete();
}
}
}
folder.delete();
}
}
RandomAccessFile
RandomAccessFile不同于File,它提供了对文件内容的访问,可以读写文件且支持随机访问文件的任意位置。
RandomAccessFile读写用到文件指针,它的初始位置为0,可以用getFilePointer()方法获取文件指针的位置。下面是RandomAccessFile常用的方法。
RandomAccessFile.png
public int read(int x) throws IOException 方法只读取一个字节,也就是x的低八位。
import java.io.File ;
import java.io.RandomAccessFile ;
public class RandomAccessFileDemo01{
// 所有的异常直接抛出,程序中不再进行处理
public static void main(String args[]) throws Exception{
File f = new File("d:" + File.separator + "test.txt") ; // 指定要操作的文件
RandomAccessFile rdf = null ; // 声明RandomAccessFile类的对象
rdf = new RandomAccessFile(f,"rw") ;// 读写模式,如果文件不存在,会自动创建
String name = null ;
int age = 0 ;
name = "zhangsan" ; // 字符串长度为8
age = 30 ; // 数字的长度为4
rdf.writeBytes(name) ; // 将姓名写入文件之中
rdf.writeInt(age) ; // 将年龄写入文件之中
name = "lisi " ; // 字符串长度为8
age = 31 ; // 数字的长度为4
rdf.writeBytes(name) ; // 将姓名写入文件之中
rdf.writeInt(age) ; // 将年龄写入文件之中
name = "wangwu " ; // 字符串长度为8
age = 32 ; // 数字的长度为4
rdf.writeBytes(name) ; // 将姓名写入文件之中
rdf.writeInt(age) ; // 将年龄写入文件之中
rdf.close() ; // 关闭
}
};
写完之后,开始读取数据。写的时候可以将一个字符串写入,读的时候需要一个个的以字节的形式读取出来。
import java.io.File ;
import java.io.RandomAccessFile ;
public class RandomAccessFileDemo02{
// 所有的异常直接抛出,程序中不再进行处理
public static void main(String args[]) throws Exception{
File f = new File("d:" + File.separator + "test.txt") ; // 指定要操作的文件
RandomAccessFile rdf = null ; // 声明RandomAccessFile类的对象
rdf = new RandomAccessFile(f,"r") ;// 以只读的方式打开文件
String name = null ;
int age = 0 ;
byte b[] = new byte[8] ; // 开辟byte数组
// 读取第二个人的信息,意味着要空出第一个人的信息
rdf.skipBytes(12) ; // 跳过第一个人的信息
for(int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 读取一个字节
}
name = new String(b) ; // 将读取出来的byte数组变为字符串
age = rdf.readInt() ; // 读取数字
System.out.println("第二个人的信息 --> 姓名:" + name + ";年龄:" + age) ;
// 读取第一个人的信息
rdf.seek(0) ; // 指针回到文件的开头
for(int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 读取一个字节
}
name = new String(b) ; // 将读取出来的byte数组变为字符串
age = rdf.readInt() ; // 读取数字
System.out.println("第一个人的信息 --> 姓名:" + name + ";年龄:" + age) ;
rdf.skipBytes(12) ; // 跳过第二个人的信息
for(int i=0;i<b.length;i++){
b[i] = rdf.readByte() ; // 读取一个字节
}
name = new String(b) ; // 将读取出来的byte数组变为字符串
age = rdf.readInt() ; // 读取数字
System.out.println("第三个人的信息 --> 姓名:" + name + ";年龄:" + age) ;
rdf.close() ; // 关闭
}
};
结果如下:
result.png
流
在Java程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成。程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件。流涉及的领域很广:标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流等等。
Stream.png
流具有方向性,至于是输入流还是输出流则是一个相对的概念,一般以程序为参考,如果数据的流向是程序至设备,我们成为输出流,反之我们称为输入流。
可以将流想象成一个“水流管道”,水流就在这管道中形成了,自然就出现了方向的概念。
Information.jpg
先上一个Java IO流类层次图,如前所述,一个流被定义为一个数据序列。输入流用于从源读取数据,输出流用于向目标写数据:
JavaIO流类层次图.png
是不是被吓到了?没关系,我们将通过一个个例子来学习这些功能。
IO流分类
1.按操作数据类型分:字符流和字节流
编码与解码:编码就是把字符转换为字节,而解码是把字节重新组合成字符。
如果编码和解码过程使用不同的编码方式那么就出现了乱码。
- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
- UTF-16be 编码中,中文字符和英文字符都占 2 个字节。
UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
Java 使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
字符流:Java中的字符流处理的最基本的单元是2字节的Unicode码元(char),它通常用来处理文本数据,如字符、字符数组或字符串等。所谓Unicode码元,也就是一个Unicode代码单元,范围是0x0000~0xFFFF。在以上范围内的每个数字都与一个字符相对应,Java中的String类型默认就把字符以Unicode规则编码而后存储在内存中。然而与存储在内存中不同,存储在磁盘上的数据通常有着各种各样的编码方式。使用不同的编码方式,相同的字符会有不同的二进制表示。实际上字符流是这样工作的:
- 输出字符流:把要写入文件的字符序列(实际上是Unicode码元序列)转为指定编码方式下的字节序列,然后再写入到文件中。
- 输入字符流:把要读取的字节序列按指定编码方式解码为相应字符序列(实际上是Unicode码元序列从)从而可以存在内存中。
也就是说,所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成。
字节流:Java中的字节流处理的最基本单位为单个字节(byte),它通常用来处理二进制数据,如果要得到字节对应的字符需要强制类型转换。
两者比较:
1.字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性较好,如果要操作中文数据等,用字符流。
2.字符流只用来处理文本数据,字节流还可以用来处理媒体数据,如视频、音频、图片等。
3.字符流的两个抽象基类为Reader和Writer,字节流的两个抽象基类为InputStream和OutputStream。它们的具体子类名以基类名为后缀进行扩展。
4.字节流在操作的时候不会用到缓冲区(内存),是直接对文件本身操作的,而字符流在操作的时候使用缓冲区。
Compare.jpg
以向一个文件输出"Hello world!"为例,我们分别使用字节流和字符流进行输出,且在使用完之后都不关闭流。
使用字节流不关闭执行:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class IOPractice {
public static void main(String[] args) throws IOException {
// 第1步:使用File类找到一个文件
File f = new File("/home/xiejunyu/"+
"桌面/text.txt");
// 第2步:通过子类实例化父类对象
OutputStream out = new FileOutputStream(f);
// 通过对象多态性进行实例化
// 第3步:进行写操作
String str = "Hello World!";
// 准备一个字符串
byte b[] = str.getBytes();
// 字符串转byte数组
out.write(b);
// 将内容输出
// 第4步:关闭输出流
// out.close();
// 此时没有关闭
}
}
1.png
此时没有关闭字节流操作,但是文件中也依然存在了输出的内容,证明字节流是直接操作文件本身的。
使用字符流不关闭执行:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class IOPractice {
public static void main(String[] args) throws IOException {
// 第1步:使用File类找到一个文件
File f = new File("/home/xiejunyu/桌面/test.txt");
// 第2步:通过子类实例化父类对象
Writer out = new FileWriter(f);
// 第3步:进行写操作
String str = "Hello World!";
// 准备一个字符串
out.write(str);
// 将内容输出
// 第4步:关闭输出流
// out.close();
// 此时没有关闭
}
}
2.png
程序运行后会发现文件中没有任何内容,这是因为字符流操作时使用了缓冲区,而在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果程序没有关闭字符流,缓冲区中的内容是无法输出的,所以得出结论:字符流使用了缓冲区,而字节流没有使用缓冲区。如果想让缓冲区中的内容输出,要么关闭流强制刷新缓冲区,要么调用flush方法冲刷缓冲区。可以简单地把缓冲区理解为一段特殊的内存。某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。
在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据。
建议:
1.虽然不关闭字节流不影响数据的输出,且后续JVM会自动回收这部分内存,但还是建议在使用完任何流对象之后关闭流。
2.使用流对象都要声明或抛出IOException
3.在创建一个文件时,如果目录下有同名文件将被覆盖
4.在写文件时,如果文件不存在,会在创建输出流对象并绑定文件时自动创建文件,不必使用File的exists方法提前检测
4.在读取文件时,必须使用File的exists方法提前检测来保证该文件已存在,否则抛出FileNotFoundException
2.按流向分:输入流和输出流
输入流:程序从输入流读取数据源。数据源包括外界(键盘、文件、网络等),即是将数据源读入到程序的通信通道。输入流主要包括两个抽象基类:InputStream(字节输入流)和Reader(字符输入流)及其扩展的具体子类。
输出流:程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络等)的通信通道。输出流主要包括两个抽象基类:OutputStream(字节输出流)和Writer(字符输出流)及其扩展的具体子类。
3.按功能分:节点流和处理流
按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类。
节点流:程序用于直接操作目标设备所对应的类叫节点流。(低级流)
处理流:程序通过一个间接流类去调用节点流类,以达到更加灵活方便地读写各种类型的数据,这个间接流类就是处理流。处理流可以看成是对已存在的流进行连接和封装的流。(高级流)
注意:在使用到处理流对流进行连接和封装时,读写完毕只需关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法。如果将节点流关闭以后再关闭处理流,会抛出IO异常。
(1) 节点流
节点流.png
- File 文件流。对文件进行读、写操作:FileReader、FileWriter、FileInputStream、FileOutputStream。
- Memory 流。
向内存数组读写数据: CharArrayReader与 CharArrayWriter、ByteArrayInputStream与ByteArrayOutputStream。
向内存字符串读写数据:StringReader、StringWriter、StringBufferInputStream。 - Pipe管道流:实现管道的输入和输出(进程间通信): PipedReader与PipedWriter、PipedInputStream与PipedOutputStream。
节点流示意图.png
(1) 处理流
处理流.png
- Buffering缓冲流:在读入或写出时,对数据进行缓存,以减少I/O的次数:BufferedReader与BufferedWriter、BufferedInputStream与BufferedOutputStream。
- Filtering 滤流:在数据进行读或写时进行过滤:FilterReader与FilterWriter、FilterInputStream与FilterOutputStream。
- Converting between Bytes and Characters 转换流:按照一定的编码/解码标准将字节流转换为字符流,或进行反向转换(Stream到Reader):InputStreamReader、OutputStreamWriter。
- Object Serialization 对象流 :ObjectInputStream、ObjectOutputStream。
- DataConversion数据流:按基本数据类型读、写(处理的数据是Java的基本类型):DataInputStream、DataOutputStream 。
- Counting计数流:在读入数据时对行记数 :LineNumberReader、LineNumberInputStream。
- Peeking Ahead预读流: 通过缓存机制,进行预读 :PushbackReader、PushbackInputStream。
- Printing打印流: 包含方便的打印方法 :PrintWriter、PrintStream。
处理流示意图.png
读取控制台输入
在Java中,从控制台输入有三种方法:
1.使用标准输入流对象System.in
System.in是System中内置的InputStream类对象,它的read方法一次只读入一个字节数据,返回0 ~ 255的一个byte值,一般用来读取一个字符,需要强制类型转换为char类型,而我们通常要取得一个字符串或一组数字,故这种方法不常用。下面给出这种方法的一个例子:
public class CharTest{
public static void main(String[] args) {
try{
System.out.print("Enter a Char:");
char i = (char)System.in.read();
System.out.println("Yout Enter Char is:" + i); }
catch(IOException e){
e.printStackTrace();
}
}
}
使用这种方法必须提供try-catch块或者在main方法首部声明IOException异常,因为System.in是一个流对象
2.使用Scanner类
Scanner类功能十分强大,可以读入字符串、整数、浮点数、布尔类型值等等。下面是例子:
public class ScannerTest{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("ScannerTest, Please Enter Name:");
String name = sc.nextLine(); //读取字符串型输入
System.out.println("ScannerTest, Please Enter Age:");
int age = sc.nextInt(); //读取整型输入
System.out.println("ScannerTest, Please Enter Salary:");
float salary = sc.nextFloat(); //读取float型输入
System.out.println("Your Information is as below:");
System.out.println("Name:" + name +"\n" + "Age:"+age
+ "\n"+"Salary:"+salary);
}
}
注意:
1.用nextXXX()读入XXX类型的数据,XXX可以是除了char外的所有基本数据类型,还可以是BigInteger或BigDecimal,其中凡是整型类型的数据还可以指定radix(进制),可以用next()和nextLine()读取一个字符串或一行字符
2.next()和nextLine()的区别:
next()
- 一定要读取到有效字符后才可以结束输入。
- 对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
- 只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
- next() 不能得到带有空格的字符串,除非用useDelimeter方法修改分隔符。
nextLine()
- 以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
- 可以获得空白。
3.可以用循环配合hasNextXXX方法判断输入是否继续
4.Scanner类没有直接提供读取一个字符的方法,如果要读取一个字符,有三种方法,一是读入一个字符串后取字符串的第一个字符,二是使用System.in的read方法,三是使用字符流读入
更多Scanner的用法之前已经在Java学习总结之Java基本程序设计结构中总结过了,不再赘述。
3.使用BufferedReader对象
可以把 System.in 包装在一个 BufferedReader 对象中来创建一个字符流。
下面是创建 BufferedReader 的基本语法:
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
其中,System.in是一个InputStream对象(字节流),使用InputStreamReader作为桥梁,将字节流转换为字符流,然后再使用BufferedReader进行进一步包装。
BufferedReader 对象创建后,我们便可以使用 read() 方法从控制台读取一个字符(读入一个用0~65535之间的整数表示的字符,需要强制类型转换为char类型,如果已到达流末尾,则返回 -1),或者用 readLine() 方法读取一个字符串。下面是例子:
public static void main(String[] args){
//必须要处理java.io.IOException异常
BufferedReader br = new BufferedReader(new InputStreamReader
(System.in ));
//java.io.InputStreamReader继承了Reader类
String read = null;
System.out.print("输入数据:");
try {
read = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("输入数据:"+read);
}
下面的程序示范了用 read() 方法从控制台不断读取字符直到用户输入 "q"。
// 使用 BufferedReader 在控制台读取字符
import java.io.*;
public class BRRead {
public static void main(String args[]) throws IOException
{
char c;
// 使用 System.in 创建 BufferedReader
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
System.out.println("输入字符, 按下 'q' 键退出。");
// 读取字符
do {
c = (char) br.read();
System.out.println(c);
} while(c != 'q');
}
}
下面的程序读取和显示字符行直到你输入了单词"end"。
// 使用 BufferedReader 在控制台读取字符
import java.io.*;
public class BRReadLines {
public static void main(String args[]) throws IOException
{
// 使用 System.in 创建 BufferedReader
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String str;
System.out.println("Enter lines of text.");
System.out.println("Enter 'end' to quit.");
do {
str = br.readLine();
System.out.println(str);
} while(!str.equals("end"));
}
}
在ACM等算法竞赛中,我们常常也会使用Java,在输入数据时有以下几点注意:
1.hasXXX等价于C++中读到文件末尾(EOF)
2.使用BufferedReader输入会比Scanner输入快十倍左右!
控制台输出
控制台的输出由 print() 和 println() 完成。这些方法都由类 PrintStream 定义,System.out 是该类的一个对象。
PrintStream 继承了 OutputStream类,并且实现了方法 write()。这样,write() 也可以用来往控制台写操作。
PrintStream 定义 write() 的最简单格式如下所示:void write(int byteval)
该方法将 byteval 的低八位字节写到流中,即System.out的write方法一次只能写一个字节(类比System.in的read方法一次只能读取一个字节)。
下面的例子用 write() 把字符 "A" 和紧跟着的换行符输出到屏幕:
import java.io.*;
// 演示 System.out.write().
public class WriteDemo {
public static void main(String args[]) {
int b;
b = 'A';//向上类型转换
System.out.write(b);
System.out.write('\n');
}
}
注意:write() 方法不经常使用,因为 print() 和 println() 方法用起来更为方便。
字节流(OutputStream、InputStream)
字节流主要是操作byte类型的数据,以byte数组为准,主要操作类是OutputStream、InputStream。
由于文件读写最为常见,我们先讨论两个重要的字节流 FileInputStream(文件输入流) 和 FileOutputStream(文件输出流),分别是抽象类InputStream和OutputStream的具体子类:
FileInputStream
该流用于从文件读取数据,它的对象可以用关键字 new 来创建。
有多种构造方法可用来创建对象。
可以使用字符串类型的文件名来创建一个输入流对象来读取文件:
InputStream f = new FileInputStream("C:/java/hello");
也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:
File f = new File("C:/java/hello");
InputStream in = new FileInputStream(f);
创建了InputStream对象,就可以使用下面的方法来读取流或者进行其他的流操作。
InputStream.png
下面是一个例子:
public static void main(String[] args) throws IOException{
InputStream f = new FileInputStream
("/home/xiejunyu/桌面/test.txt");
int c = 0;
while((c = f.read()) != -1)
//这里也可以先用available方法得到可读的字节数
System.out.println((char)c);
}
当我们需要创建一个byte[]来保存读取的字节时,如果数组太小,无法完整读入数据,如果太大又会造成内存浪费。可以使用File类的length方法得到文件的数据字节数,从而有效确定byte数组的大小。
public static void main(String[] args) {
// 创建一个FileInputStream对象
try {
FileInputStream fis = new FileInputStream("/home/xiejunyu/桌面/test.txt");
byte[] b=new byte[100];
fis.read(b,0,5);
System.out.println(new String(b));
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
注意: 每调用一次read方法,当前读取在文件中的位置就会向后移动一个字节或者移动byte[]的长度(read的两个重载方法),已经到文件末尾会返回-1,可以通过read方法返回-1判断是否读到文件末尾,也可以使用available方法返回下一次可以不受阻塞读取的字节数来读取。FileInputStream不支持mark和reset方法进行重复读取。BufferedInputStream支持此操作。
FileOutputStream
该类用来创建一个文件并向文件中写数据。
如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。
有两个构造方法可以用来创建 FileOutputStream 对象。
使用字符串类型的文件名来创建一个输出流对象:
OutputStream f = new FileOutputStream("C:/java/hello")
也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:
File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f);
之前的所有操作中,如果重新执行程序,则肯定会覆盖文件中的已有内容,那么此时就可以通过FileOutputStream向文件中追加内容,FileOutputStream的另外一个构造方法:
public FileOutputStream(File file,boolean append)
在构造方法中,如果将append的值设置为true,则表示在文件的末尾追加内容。程序代码如下:
File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f,true);
创建OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。
FileOutputStream.png
当有一个字符串时,可以用getBytes方法转为byte数组用于字节流的输出。
下面是一个演示 InputStream 和 OutputStream 用法的例子:
import java.io.*;
public class FileStreamTest{
public static void main(String args[]){
try{
byte bWrite[] = "ABC".getBytes();
OutputStream os = new FileOutputStream("/home/xiejunyu/桌面/test.txt");
for(int x=0; x < bWrite.length ; x++){
os.write(bWrite[x] ); // writes the bytes
}
os.close();
InputStream is = new FileInputStream("/home/xiejunyu/桌面/test.txt");
int size = is.available();
for(int i=0; i< size; i++){
System.out.print((char)is.read() + " ");
}
is.close();
}catch(IOException e){
System.out.print("Exception");
}
}
}
上面的程序首先创建文件test.txt,并把给定的数字以二进制形式写进该文件,同时输出到控制台上。
以上代码由于是二进制写入,可能存在乱码,你可以使用以下代码实例来解决乱码问题:
import java.io.*;
public class fileStreamTest2{
public static void main(String[] args) throws IOException {
File f = new File("a.txt");
FileOutputStream fop = new FileOutputStream(f);
// 构建FileOutputStream对象,文件不存在会自动新建;如果存在会覆盖原文件
OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
// 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk
writer.append("中文输入");
// 写入到缓冲区
writer.append("\r\n");
//换行
writer.append("English");
// 刷新缓冲区,写入到文件,如果下面已经没有写入的内容了,直接close也会写入
writer.close();
//关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉
fop.close();
// 关闭输出流,释放系统资源
FileInputStream fip = new FileInputStream(f);
// 构建FileInputStream对象
InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
// 构建InputStreamReader对象,编码与写入相同
StringBuffer sb = new StringBuffer();
while (reader.ready()) {
sb.append((char) reader.read());
// 转成char加到StringBuffer对象中
}
System.out.println(sb.toString());
reader.close();
// 关闭读取流
fip.close();
// 关闭输入流,释放系统资源
}
}
以上例子证明:在对多国语言的支持上,字符流表现更优,此时应使用字符流而不是字节流。
还可以用InputStream和OutputStream配合进行文件的复制,即读取原件数据,写入副本文件。
复制有两种实现方式:
实现一:将源文件中的内容全部读取进来,之后一次性的写入到目标文件
实现二:边读边写
在实际开发中建议使用边读边写的方式,代码如下:
public static void main(String[] args) {
// 文件拷贝
try {
FileInputStream fis=new FileInputStream("happy.gif");
FileOutputStream fos=new FileOutputStream("happycopy.gif");
int n=0;
byte[] b=new byte[1024];
while((n=fis.read(b))!=-1){
fos.write(b,0,n); //n是实际读取到的字节数,如果写fos.write(b),会造成最后一次数组未满的情况也写1024个字节,从而造成副本比原件略大
}
fis.close();
fos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
实际上边读边写也分为三种方式:
1.批量拷贝(循环读取,每次读入一个byte数组)
2.缓冲拷贝(使用缓冲流)
3.批量+缓冲拷贝(循环批量读取到字节数组中,然后使用缓冲输出流写入到文件)
第三种方式是最快的。
注意:InputStream的int read()方法读取一个字节,并用这个字节填充整型的低八位并返回,OutputStream的void write(int x)写入x的低八位,如果要写入一个int,需要移位并写4次。读写基本数据类型建议使用DataInputStream和DataOutputStream。小编推荐一个学Java的学习社区【 戳我立即加入 】,无论你是大牛还是小白,是想转行还是想入行都可以来了解一起进步一起学习!裙内有开发工具,很多干货和技术资料分享!