目录
流
“流”是一个抽象的概念,它是对输入输出设备的一种抽象理解,在java中,对数据的输入输出操作都是以“流”的方式进行的。“流”具有方向性,输入流、输出流是相对的。当程序需要从数据源中读入数据的时候就会开启一个输入流,相反,写出数据到某个数据源目的地的时候也会开启一个输出流。数据源可以是文件、内存或者网络等。
流”序列中的数据可以是未经加工的原始二进制数据,也可以是经过一定编码处理后符合某种格式的特定数据,因此java中的“流”分为两种流:
- 字节流***:数据流中的最小的数据单元是字节***,一次读入读出8位二进制;
- 字符流***: 数据流中的最小的数据单元是字符***,一次读入读出16位二进制,java中的字符是Unicode编码|AscII编码。
输入流
抽象基本组件是InputStream类
InputStream
|
+--FileInputStream
|
+--ByteArrayInputStream
|
+--PipedInputStream
|
+--FilterInputStream
|
+--BufferedInputStream
|
+--PushbackInputStream
|
+--DataInputStream
|
+--ObjectInputStream
我们有FileInputStream,ByteArrayInputStream和PipedInputStream,FilterInputStream的具体类。
超类InputStream包含从输入流读取数据的基本方法,所有具体类都支持这些方法。
对输入流的基本操作是从其读取数据。 InputStream类中定义的一些重要方法在下表中列出。
ID | 方法/说明 |
---|---|
read() | 读取一个字节并将读取的字节作为int返回。 当到达输入流的结尾时,它返回-1。 |
read(byte[] buffer) | 读取最大值直到指定缓冲区的长度。 它返回在缓冲区中读取的字节数。 如果到达输入流的结尾,则返回-1。 |
read(byte [] buffer,int offset,int length) | 读取最大值到指定长度字节。 数据从偏移索引开始写入缓冲区。 它返回读取的字节数或-1,如果到达输入流的结束。 |
close() | 关闭输入流 |
available() | 返回可以从此输入流读取但不阻塞的估计字节数。 |
文件输入流
在Java I/O中,流意味着数据流。流中的数据可以是字节,字符,对象等。
要从文件读取,我们需要创建一个FileInputStream类的对象,它将表示输入流。
InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
文件字节输入流 FileInputStream 就是其中最常用的子类,该类最常用的方法是 read 方法,该方法每次调用都需要传入一个 byte[] 并返回该次读取到的字节数量,如果读取到达文件末尾则返回 -1。
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
File file = new File("C:/file/log.txt");
InputStream inputStream = new FileInputStream(file);
byte[] bytes = new byte[1024];
int n = 0;
while ((n = inputStream.read(bytes)) != -1) {
System.out.println(n);
String s = new String(bytes, 0, n);
System.out.println(s);
}
inputStream.close();
}
}
字节输入流一般针对文本文件和二进制文件的读取操作。但是需要注意以下几点:
- 字节输入流读取文件时,一般前几次返回的读取的字节数量都为字节数组的长度,只有最后一次可能到达不了数组的长度。
- 字节输入流可以获取文件的内容,针对字符文件可以将字节数组转为字符串输出 new String(bytes,0,n),但是有时可能会将中文字符切成两半而出现乱码。
- UFT-8 编码中,大部分中文占用三个字节,可以使用字符串的 getBytes 方法获取任意字符串在 UTF-8 编码时的字节序列(数组)。
- 字节输入流读取二进制文件后转换为字符串后输出为乱码。
- 如果文件编码是 ANSI 编码,则输出时使用字符编码 GB2312,否则输出乱码。new String(bytes,0,n,"GB2312")。win7 系统文本文件默认为 ANSI 编码。
缓冲输入流
BufferedInputStream通过缓冲数据向输入流添加功能。
它维护一个内部缓冲区以存储从底层输入流读取的字节。
我们创建缓冲区输入流如下:
String srcFile =“test.txt";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
以下代码显示如何使用BufferedInputStream从文件读取。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class Main {
public static void main(String[] args) {
String srcFile = "test.txt";
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
srcFile))) {
// Read one byte at a time and display it
byte byteData;
while ((byteData = (byte) bis.read()) != -1) {
System.out.print((char) byteData);
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
推回输入流
PushbackInputStream向输入流添加功能,允许我们使用其unread()方法推回读取的字节。
有三个版本的unread()方法。一个让我们推回一个字节,另外两个让我们推回多个字节。
import java.io.FileInputStream;
import java.io.PushbackInputStream;
public class Main {
public static void main(String[] args) {
String srcFile = "test.txt";
try (PushbackInputStream pis = new PushbackInputStream(new FileInputStream(
srcFile))) {
byte byteData;
while ((byteData = (byte) pis.read()) != -1) {
System.out.print((char) byteData);
pis.unread(byteData);
// Reread the byte we unread
byteData = (byte) pis.read();
System.out.print((char) byteData);
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
数据输入流
DataInputStream可以从输入流中读取Java基本数据类型值。
DataInputStream类包含读取数据类型值的读取方法。例如,要读取int值,它包含一个readInt()方法;读取char值,它有一个readChar()方法等。它还支持使用readUTF()方法读取字符串。
以下代码显示了如何从文件读取原始值和字符串。
import java.io.DataInputStream;
import java.io.FileInputStream;
public class Main {
public static void main(String[] args) {
String srcFile = "primitives.dat";
try (DataInputStream dis = new DataInputStream(new FileInputStream(srcFile))) {
// Read the data in the same order they were written
int intValue = dis.readInt();
double doubleValue = dis.readDouble();
boolean booleanValue = dis.readBoolean();
String msg = dis.readUTF();
System.out.println(intValue);
System.out.println(doubleValue);
System.out.println(booleanValue);
System.out.println(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出流
在抽象超类OutputStream中定义了三个重要的方法:write(),flush()和close()。
write()方法将字节写入输出流。它有三个版本,允许我们一次写一个字节或多个字节。
flush()方法用于将任何缓冲的字节刷新到数据宿。
close()方法关闭输出流。
文件输出流
要写入文件,我们需要创建一个FileOutputStream类的对象,它将表示输出流。
OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
文件字节输出流 FileOutputStream 就是其中最常用的子类,该类有一个常用的方法 write 用于将字节数组写入文件。如果在写入过程中需要换行可以使用转义字符 \n 表示。在不关闭流的情况下每次 write 的内容都会追加在文件中。如果输出流指定的文件不存在,则文件创建输出流时会自动创建该文件,但是前提是父路径必须存在,不可以将输出流的路径直接指向文件或盘符。
public class FileOutputStreamTest {
public static void main(String[] args) throws Exception {
File f = new File("C:\\file\\ss.txt");//直接覆盖写同一个文件
//字节输出流
OutputStream outputStream = new FileOutputStream(f);
String s = "hello,world!\r\n";
String s1 = "中国人";
outputStream.write(s.getBytes());
outputStream.write(s1.getBytes());
outputStream.close();
}
}
注意:在程序运行期间如果不手动关闭流,则该文件会一直占用,无法删除。
如果文件包含数据,数据将被擦除。为了保留现有数据并将新数据附加到文件,我们需要使用FileOutputStream类的另一个构造函数,它接受一个布尔标志,用于将新数据附加到文件。
要将数据附加到文件,请在第二个参数中传递true,使用以下代码。
FileOutputStream fos = new FileOutputStream(destFile, true);
缓冲输出流
在字符文件操作方面为了提高效率引入了缓冲字符流 BufferedReader 和 BufferedWriter。它们操作文件的基本单位可以升级为一行,无论该文件一行有多少字符都能通过 readLine 读取,同时写入时可以直接指定任意的字符串。
import java.io.*;
public class BufferedCopyTxtFile {
public static void main(String[] args) throws Exception {
FileReader reader = new FileReader("C:\\ff\\hsp.txt");
BufferedReader bufferedReader = new BufferedReader(reader);
FileWriter writer = new FileWriter("C:\\hsp1.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer);
String s = "";
while ((s = bufferedReader.readLine()) != null) {
bufferedWriter.write(s + "\n");
}
bufferedReader.close();
bufferedWriter.close();
reader.close();
writer.close();
}
}
关于输入流与输出流关闭问题需要注意以下几点:
1.输入流如果不关闭。在进程运行期间,该文件一直被占用,无法删除。
2.输出流不关闭文件内容将不能正常保存到该文件中。
3.流的关闭顺序一般要求与打开顺序相反,即:后开先关。
properties 配置文件的读取
在实际开发中经常会把一些易发生改变的配置信息放到配置文件中(如:数据库的连接信息,文件上传的位置信息等),一般在 java 中配置文件常用properties 格式,该文件需要在 src 下创建。如下,在 src 下创建 application.properties 文件。
id=12
name=wpf<br/>
读取配置文件里面值
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class PropertiesTest {
public static void main(String[] args) throws IOException {
InputStream inputStream = PropertiesTest.class.getClassLoader().getResourceAsStream( "application.properties" );
//读取src下名字叫thisFile的文件
Properties properties = new Properties();
//作用是从文件流里面获取键值对
properties.load( inputStream );//加载文件流
System.out.println( properties.getProperty( "name" ));
stream.close();
}
}
轻松使用 properties.getProperty( "key" ) 方法就可以通过健获取文件流里面的值。
对象序列化和反序列化
Java 序列化是指把 Java 对象转换为字节序列 byte[] 的过程;而 Java 反序列化是指把字节序列恢复为 Java 对象的过程。
在程序运行过程中,经常会把对象序列化到到磁盘文件中,以保存对象数据。在需要的时候再从文件中反序列化出来,完成对象的持久化。要想类的对象能够序列化和反序列化,该类必须实现 Serializable 接口,否则会报 NotSerializableException。
案例:
import java.io.*;
import java.util.Scanner;
public class SerializableTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// writeObject();
readObject();
}
private static void readObject() throws IOException, ClassNotFoundException {
FileInputStream inputStream = new FileInputStream("C:/person.txt");
ObjectInputStream stream = new ObjectInputStream(inputStream);
//获取对象,由于无法判断对象的类型,故采用 Object 类型接收。
Object o = stream.readObject();
//确定是 Person 类型后可以将之强转为 Person 类型
Person person = (Person) o;
System.out.println(person);
stream.close();
inputStream.close();
}
private static void writeObject() throws IOException {
//Scanner 专门用于接收控制台输入的数据
Scanner scanner = new Scanner(System.in);
FileOutputStream outputStream = new FileOutputStream("C:/person.txt");
ObjectOutputStream stream = new ObjectOutputStream(outputStream);
System.out.println("请输入 id");
//通过 scanner 获取一个整数,程序运行到该位置会自动卡住,直到用户输入内容后回车程序才能继续。
int id = scanner.nextInt();
System.out.println("请输入名字");
String name = scanner.next();
System.out.println("请输入密码");
String password = scanner.next();
Person person = new Person(id, name, password);
//序列化对象到文件
stream.writeObject(person);
//刷新
stream.flush();
//关闭资源
stream.close();
outputStream.close();
scanner.close();
}
}
class Person implements Serializable {
private int id;
private String name;
private String password;
...省略 构造方法、get、set 与 toString 方法。
}
注意:对象写入和读出之间不能修改类的结构,否则会报:InvalidClassException。
java.io.InvalidClassException: cn.hx.Person; local class incompatible:
stream classdesc serialVersionUID = 2431545036432879007, local class serialVersionUID = -2205805426406937374