上节介绍常规的字节流、字符流,本节介绍特殊的IO流。
数据操作流
数据操作流是操作基本数据类型的流。
public class DataInputStream
extends FilterInputStream
public class DataOutputStream
extends FilterOutputStream
-
DataInputStream 构造方法
- public DataInputStream(InputStream in)
-
特殊方法
- boolean
readBoolean()
:读取一个输入字节,如果该字节不是零,则返回 true,如果是零,则返回 false。 - byte
readByte()
:读取并返回一个输入字节。 - char
readChar()
:读取两个输入字节并返回一个 char 值。 - short
readShort()
:读取两个输入字节并返回一个 short 值。 - int
readInt()
:读取四个输入字节并返回一个 int 值。 - long
readLong()
:读取八个输入字节并返回一个 long 值。 - float
readFloat()
:读取四个输入字节并返回一个 float 值。 - double
readDouble()
:读取八个输入字节并返回一个 double 值
- boolean
-
DataOutputStream 构造方法
- public DataOutputStream(OutputStream out)
-
特殊方法
- void
write
(int b) - void
writeBoolean
(boolean v) - void
writeByte
(int v) - void
writeBytes
(String s):将字符串按字节顺序写出到基础输出流中 - void
writeChar
(int v) - void
writeChars
(String s):将字符串按字符顺序写入基础输出流。 - void
writeInt
(int v) - void
writeDouble
(double v)
- void
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
// 先写入,在读取。
// 写
write();
// 读
read();
}
private static void read() throws IOException {
// DataInputStream(InputStream in)
// 创建数据输入流对象
DataInputStream dis = new DataInputStream(
new FileInputStream("E:\\demo\\a.txt"));
// 读数据
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
char c = dis.readChar();
boolean bb = dis.readBoolean();
// 释放资源
dis.close();
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(c);
System.out.println(bb);
}
private static void write() throws IOException {
// DataOutputStream(OutputStream out)
// 创建数据输出流对象
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"E:\\demo\\a.txt"));
// 写数据了
dos.writeByte(10);
dos.writeShort(100);
dos.writeInt(1000);
dos.writeLong(10000);
dos.writeFloat(12.34F);
dos.writeDouble(12.56);
dos.writeChar('a');
dos.writeBoolean(true);
// 释放资源
dos.close();
}
}
输出:
10
100
1000
10000
12.34
12.56
a
true
数据操作流是操作基本数据类型的流,输出流怎么写入的数据,输入流必须按照顺序读取数据,不然造成读取异常。
内存操作流
内存操作流:用于处理临时存储信息,程序结束,数据就从内存中消失。
字节数组
public class ByteArrayInputStream
extends InputStream
public class ByteArrayOutputStream
extends OutputStream
- ByteArrayInputStream
- public ByteArrayInputStream(byte[] buf):使用 字节数组 作为其缓冲区数组
- public ByteArrayInputStream(byte[] buf, int offset, int length)
- ByteArrayOutputStream
- public ByteArrayOutputStream()
- public ByteArrayOutputStream(int size):创建一个新的 byte 数组输出流,指定大小的缓冲区容量
- public synchronized byte[]
toByteArray()
:将输出流转换成字节数组,用于输入流 ByteArrayInputStream 读取。
字符数组
public class CharArrayReader
extends Reader
public class CharArrayWriter
extends Writer
- CharArrayReader
- public CharArrayReader(char[] buf) :使用 字符数组 作为其缓冲区数组
- public CharArrayReader(char[] buf, int offset, int length)
- CharArrayWriter
- public CharArrayWriter()
- public CharArrayWriter(int initialSize):创建一个具有指定初始大小的新 CharArrayWriter
- public char[]
toCharArray()
:将输出流转换成字符数组,用于输入流 CharArrayReader 读取。
字符串
public class StringReader
extends Reader
public class StringWriter
extends Writer
- StringReader
- public StringReader(String s) :使用 字符串 作为其缓冲区
- StringWriter
- public StringWriter()
- public StringWriter(int initialSize)
- public String
toString()
:将输出流转换成字符串,用于输入流 StringReader 读取。
public class MemoryDemo {
public static void main(String[] args) throws IOException {
// 写数据
// ByteArrayOutputStream()
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 写数据
for (int x = 0; x < 10; x++) {
baos.write(("hello" + x).getBytes());
}
// public byte[] toByteArray() 将字节输出流转换成字节数组!
byte[] bys = baos.toByteArray();
// 读数据
// ByteArrayInputStream(byte[] buf) 转换后的字节数组用于字节输入流的参数!
ByteArrayInputStream bais = new ByteArrayInputStream(bys);
int by = 0;
while ((by = bais.read()) != -1) {
System.out.print((char) by);
}
}
}
输出:
hello0hello1hello2hello3hello4hello5hello6hello7hello8hello9
注:内存操作流无需close()关闭资源。
打印流(常用)
字节打印流:public class PrintStream
extends FilterOutputStream
字符打印流:public class PrintWriter
extends Writer
- PrintStream 构造方法
- PrintStream(File file):创建指定文件对象File和默认编码方式的打印流。不自动刷新。
- PrintStream(File file,
String csn
):创建指定文件对象File和字符集的打印流。不自动刷新。 - PrintStream(String fileName) :创建指定文件名称和默认编码方式的打印流。不自动刷新。
- PrintStream(String fileName,
String csn
):创建指定文件名称和字符集的打印流。不自动刷新。 - PrintStream(OutputStream out):创建指定字节输出流和默认编码方式的打印流。不自动刷新。
- PrintStream(OutputStream out,
boolean autoFlush
):创建指定字节输出流和默认编码方式的打印流。指定是否自动刷新。 - PrintStream(OutputStream out,
boolean autoFlush
,String encoding
):创建指定字节输出流和字符集的打印流。指定是否自动刷新。
- PrintWriter 构造方法
- PrintWriter(File file):创建指定文件对象File和默认编码方式的字符打印流。不自动刷新。
- PrintWriter(File file,
String csn
):创建指定文件对象File和字符集的字符打印流。不自动刷新。 - PrintWriter(String fileName):
- PrintWriter(String fileName,
String csn
) : - PrintWriter(OutputStream out):创建指定字节输出流和默认编码方式的字符打印流。不自动刷新。
- PrintWriter(OutputStream out,
boolean autoFlush
) :创建指定字节输出流和默认编码方式的字符打印流。指定是否自动刷新。 - PrintWriter(Writer out):创建指定字符输出流的字符打印流。不自动刷新。
- PrintWriter(Writer out,
boolean autoFlush
) :创建指定字符输出流的字符打印流。指定是否自动刷新。可以给 out 传参OutputStreamWriter,以达到设置文件字符编码。
- 特有的方法
- void
print(任意类型)
:写入任意类型的值。 - void
println(任意类型)
:写入任意类型的值并换行。 format
(String format, Object … args):格式化字符串写入。printf
(String format, Object … args):格式化字符串写入。相当于 C 语言的 sprintf。
- void
如果需要使用带自动刷新的流,第一个参数需要是 OutputStream/Writer ,第二个参数是true。
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
// 作为Writer的子类使用
PrintWriter pw = new PrintWriter("E:\\demo\\a.txt");
pw.write("hello");
pw.write("world");
pw.println("java");
pw.println("111");
pw.close();
// 创建打印流对象
PrintWriter pw2 = new PrintWriter(new FileWriter("E:\\demo\\b.txt"), true);
pw2.println("hello");
pw2.println(true);
pw2.println(100);
pw2.close();
// public PrintStream(String fileName, String csn) 创建带字符编码的文件
PrintStream pw3 = new PrintStream("E:\\demo\\gbk1.txt", "GBK"); //文件gbk1.txt生成成功,且文件编码是GBK
pw3.println("hello");
pw3.printf("name=%s,age=%d", "小明", 15);
pw3.close();
// public PrintStream(OutputStream out, boolean autoFlush, String encoding) 创建带字符编码的文件
PrintStream pw4 = new PrintStream(
new FileOutputStream("E:\\demo\\gbk2.txt"), true, "GBK"); //文件gbk2.txt生成成功,且文件编码是GBK
pw4.println("hello");
pw4.printf("name=%s,age=%d", "小明", 15);
pw4.close();
// public PrintWriter(String fileName, String csn) 创建带字符编码的文件
PrintWriter pw5 = new PrintWriter("E:\\demo\\gbk3.txt", "GBK"); //文件gbk3.txt生成成功,且文件编码是GBK
pw5.println("hello");
pw5.printf("name=%s,age=%d", "小明", 15);
pw5.close();
// public PrintWriter (Writer out) 创建带字符编码的文件,OutputStreamWriter设置字符编码
PrintWriter pw6 = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream("E:\\demo\\gbk4.txt"), "GBK")); //文件gbk4.txt生成成功,且文件编码是GBK
pw6.println("hello");
pw6.printf("name=%s,age=%d", "小明", 15);
pw6.close();
}
}
打印流特点:
1、只有写数据的,没有读取数据。
2、可以操作任意类型的数据。
3、该流是可以直接操作文本文件的。
哪些流可以直接操作文本?
FileInputStream
、FileOutputStream
、FileReader
、FileWriter
、PrintStream
、PrintWriter
。看API,查流对象的构造方法,如果同时有File类型和String类型的参数,一般来说就是可以直接操作文件的。
注:输出流 PrintStream、PrintWriter 可以传参File、String、OutputStream,可以指定字符集,可以指定是否自动刷新,还可以写入任意类型数据。
问题:PrintStream、PrintWriter构造函数没有 “是否追加方式写入文件” ,怎么让 PrintStream、PrintWriter 可以追加呢?
// PrintStream
PrintStream pw7 = new PrintStream(
new FileOutputStream("E:\\demo\\gbk2.txt", true), true, "GBK");
// PrintWriter
PrintWriter pw8 = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream("E:\\demo\\gbk4.txt", true), "GBK"));
标准输入输出流
System类中的两个成员变量:
public static final InputStream in
“标准”输入流。
public static final PrintStream out
“标准”输出流。
InputStream is = System.in;
PrintStream ps = System.out;
问题:首先,标准输入流是InputStream,而我们怎么把标准输入一次获取一行?
思路:1、每次获得一行方法BufferedReader.readLine()。
2、BufferedReader构造方法BufferedReader(Reader in)。
3、而标准输入流是InputStream,需要通过转换流 InputStreamReader 转换成 Reader。
标准输入流
public class SystemDemo {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入一个字符串:");
String line = br.readLine();
System.out.println("你输入的字符串是:" + line);
System.out.println("请输入一个整数:");
line = br.readLine();
int i = Integer.parseInt(line);
System.out.println("你输入的整数是:" + i);
}
}
请输入一个字符串:
1111
你输入的字符串是:1111
请输入一个整数:
12
你输入的整数是:12
标准输出流
public class SystemDemo {
public static void main(String[] args) throws IOException {
System.out.println("helloworld");
// 获取标准输出流对象
PrintStream ps = System.out;
ps.println("helloworld");
// 使用缓存输出流把数据输出到控制台
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write("helloworld");
bw.newLine();
bw.flush();
bw.close();
}
}
输出:
helloworld
helloworld
helloworld
随机访问流
public class RandomAccessFile
extends Object
RandomAccessFile类不属于流,是Object类的子类。但它融合了 InputStream 和 OutputStream 的功能,支持对文件的随机访问读取和写入。
RandomAccessFile 构造方法
- RandomAccessFile(File file,
String mode
):第一个参数是文件路径,第二个参数是操作文件的模式。 - RandomAccessFile(String name,
String mode
)
mode 参数指定用以打开文件的访问模式
值 | 含意 |
---|---|
r | 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。 |
rw | 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 |
rws | 打开以便读取和写入。对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 |
rwd | 打开以便读取和写入。对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。 |
public class SystemDemo {
public static void main(String[] args) throws IOException {
write();
read();
}
private static void read() throws IOException {
// 创建随机访问流对象
RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");
int i = raf.readInt();
System.out.println(i);
// 该文件指针可以通过 getFilePointer方法读取,并通过 seek 方法设置。
System.out.println("当前文件的指针位置是:" + raf.getFilePointer());
char ch = raf.readChar();
System.out.println(ch);
System.out.println("当前文件的指针位置是:" + raf.getFilePointer());
String s = raf.readUTF();
System.out.println(s);
System.out.println("当前文件的指针位置是:" + raf.getFilePointer());
// 我不想重头开始了,我就要读取a,怎么办呢?
raf.seek(4);
ch = raf.readChar();
System.out.println(ch);
}
private static void write() throws IOException {
// 创建随机访问流对象
RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");
// 怎么玩呢?
raf.writeInt(100);
raf.writeChar('a');
raf.writeUTF("中国");
raf.close();
}
}
输出:
100
当前文件的指针位置是:4
a
当前文件的指针位置是:6
中国
当前文件的指针位置是:14
a
- void
seek(long pos)
:设置到此文件开头测量到的文件指针偏移量。 - long
getFilePointer()
:返回此文件中的当前偏移量。
合并流
public class SequenceInputStream
extends InputStream
SequenceInputStream构造方法
- SequenceInputStream(
InputStream s1
,InputStream s2
) - SequenceInputStream(
Enumeration<? extends InputStream> e
)
public class SequenceInputStreamDemo {
public static void main(String[] args) throws IOException {
// SequenceInputStream(InputStream s1, InputStream s2)
InputStream s1 = new FileInputStream("E:\\demo\\1.txt");
InputStream s2 = new FileInputStream("E:\\demo\\2.txt");
SequenceInputStream sis = new SequenceInputStream(s1, s2);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("E:\\demo\\Copy.txt"));
// 如何写读写呢,其实很简单,你就按照以前怎么读写,现在还是怎么读写
byte[] bys = new byte[1024];
int len = 0;
while ((len = sis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
sis.close();
}
}
生成的 Copy.txt 文件内容是 1.txt 和 2.txt 合并后内容。
问题:如果有多个文件需要合并呢?
方法一:嵌套使用SequenceInputStream。
InputStream s3 = new FileInputStream("E:\\demo\\3.txt");
SequenceInputStream sis2 = new SequenceInputStream(sis, s3);
如果文件特别多,会使代码臃肿。SequenceInputStream 提供另一种构造方法:
方法二:Vector集合
public class SequenceInputStreamDemo {
public static void main(String[] args) throws IOException {
// SequenceInputStream(InputStream s1, InputStream s2)
InputStream s1 = new FileInputStream("E:\\demo\\1.txt");
InputStream s2 = new FileInputStream("E:\\demo\\2.txt");
InputStream s3 = new FileInputStream("E:\\demo\\3.txt");
Vector<InputStream> vector = new Vector<InputStream>();
vector.add(s1);
vector.add(s2);
vector.add(s3);
Enumeration<InputStream> elements = vector.elements();
SequenceInputStream sis = new SequenceInputStream(elements);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("E:\\demo\\Copy.java"));
// 如何写读写呢,其实很简单,你就按照以前怎么读写,现在还是怎么读写
byte[] bys = new byte[1024];
int len = 0;
while ((len = sis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
sis.close();
}
}
使用集合vector.elements()
方法为 SequenceInputStream(Enumeration<? extends InputStream> e) 提供构造参数。
序列化流
序列化流:把对象按照流一样的方式写入文本文件或者在网络中传输。 对象 – 流数据
public class ObjectOutputStream
extends OutputStream
反序列化流:读取文本文件中的流对象数据或者网络中的流对象数据还原成对象。 流数据 – 对象
public class ObjectInputStream
extends InputStream
- ObjectOutputStream构造方法
- protected ObjectOutputStream()
- public ObjectOutputStream(OutputStream out)
- 常用方法
- public final void
writeObject(Object obj)
:将指定的对象写入 ObjectOutputStream。
- public final void
- ObjectInputStream构造方法
- protected ObjectInputStream()
- public ObjectInputStream(InputStream in)
- 常用方法
- public final Object
readObject()
:从 ObjectInputStream 读取对象。 - public ObjectInputStream.GetField
readFields()
:按名称从流中读取持久字段并使其可用。- abstract int
get
(String name,int val
):从持久字段获取指定字段的值。val:name 没有值时使用的默认值。可以多种数据类型 - abstract short
get
(String name,short val
):从持久字段获取指定字段的值。 - abstract Object
get
(String name,Object val
):从持久字段获取指定字段的值。
- abstract int
- public final Object
读写单个对象
Stu:
public class Stu implements Serializable {
public static final long serialVersionUID = 1L;
public String name;
public int age;
setter、getter方法
}
public class ObjectDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化
// write();
read();
}
private static void write() throws IOException {
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\demo\\1.txt"));
// 创建对象
Stu p = new Stu("lili", 27);
// public final void writeObject(Object obj)
oos.writeObject(p);
// 释放资源
oos.close();
}
private static void read() throws IOException, ClassNotFoundException {
// 创建反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\demo\\1.txt"));
// 读取对象
Object obj = ois.readObject();
// 释放资源
ois.close();
// 输出对象
System.out.println(obj);
}
}
输出:
Person [name=lili, age=27]
类通过实现 java.io.Serializable
接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。Serializable接口居然没有任何方法,类似于这种没有方法的接口被称为标记接口。
注:如果一个类需要序列化,其成员变量类也必须实现Serializable接口。
新增成绩类,声明 Score 类为Stu 的成员变量。
public class Score{
public String course;
public String grade;
public Score(String course, String grade) {
this.course = course;
this.grade = grade;
}
@Override
public String toString() {
return "Score{" + "course='" + course + '\'' + ", grade='" + grade + '\'' + '}';
}
}
public class Stu implements Serializable {
public static final long serialVersionUID = 1L;
public String name;
public int age;
public Score score;
...
}
运行结果:
Exception in thread "main" java.io.NotSerializableException: 文件.Score
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at 文件.ObjectDemo.write(ObjectDemo.java:23)
at 文件.ObjectDemo.main(ObjectDemo.java:10)
报错:Score 类没有实现 Serializable接口。
修改类Score,让其实现 Serializable 接口。(其实只是类声明实现下 Serializable 接口,也不用重写任何方法,因为 Serializable 是个标记接口)
public class Score implements Serializable {
public static final long serialVersionUID = 1L; //可加可不加
public String course;
public String grade;
public Score(String course, String grade) {
this.course = course;
this.grade = grade;
}
@Override
public String toString() {
return "Score{" + "course='" + course + '\'' + ", grade='" + grade + '\'' + '}';
}
}
运行结果:
Stu{name='lili', age=28, score=Score{course='math', grade='90'}}
Stu{name='中文', age=20, score=Score{course='math', grade='91'}}
Stu{name='jack', age=28, score=Score{course='math', grade='92'}}
serialVersionUID
每次修改 java 文件的内容的时候,class文件的 id 值都会发生改变。如果写入对象后,对象 java 文件有过修改,相应的 class 文件的 id 改变,再读取对象时就会报错。
通过设置 java 文件的 serialVersionUID,可以使 java 文件改变,其对应 class 文件的 id 不改变。
serialVersionUID有两种显示的生成方式:
- 默认的1L,比如:
private static final long serialVersionUID = 1L;
- 根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final long serialVersionUID = xxxxL;
一般使用默认的1L,这样可以通过serialVersionUID知道该类的版本号。
1、在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
2、在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
transient
transient
关键字声明不需要序列化的成员变量。
Stu:
public class Stu implements Serializable {
public static final long serialVersionUID = 1L;
public String name;
transient public int age;
setter、getter方法
}
输出:
Person [name=lili, age=0]
从结果看出,age 属性变为了 0,说明被transient
关键字修饰之后没有被序列化。底层原理上,serialization就是把对象的状态存储到硬盘上去,等需要的时候就可以再把它读出来使用。而transient
修饰变量的生命周期仅存于调用者的内存中,而不会写到磁盘里持久化。
static
静态变量不管有没有被transient关键字修饰,都会被序列化。
因为静态变量在全局区,而我们的序列化是写到磁盘上的,JVM 查找这个静态变量的值,是从全局区查找的,而不是磁盘上。
读写多个对象
public class ObjectDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化
write();
read();
}
private static void write() throws IOException {
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\demo\\1.txt"));
// 创建对象
Stu p = new Stu("lili", 28);
// public final void writeObject(Object obj)
oos.writeObject(p);
oos.writeObject(new Stu("中文",20));
oos.writeObject(new Stu("jack",28));
// 释放资源
oos.close();
}
private static void read() throws IOException, ClassNotFoundException {
// 创建反序列化对象
FileInputStream fileInputStream = null;
fileInputStream = new FileInputStream("E:\\demo\\1.txt");
ObjectInputStream ois = new ObjectInputStream(fileInputStream);
// 读取对象
Object obj = null;
while (fileInputStream.available() > 0) {
// 输出对象
obj = ois.readObject();
System.out.println(obj);
}
// 释放资源
ois.close();
}
}
输出:
Person [name=lili, age=28]
Person [name=中文, age=20]
Person [name=jack, age=28]
fileInputStream.available() > 0
,判断是否有可读对象。
Properties
public class Properties
extends Hashtable<Object,Object>
属性集合类。是一个可以和 IO 流相结合使用的集合类。可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
- Properties构造方法
- Properties():创建一个无默认值的空属性列表。
- Properties(Properties defaults):创建一个带有指定默认值的空属性列表。
- 特有方法
- public Object
setProperty(String key, String value)
:添加元素 - public String
getProperty(String key)
:获取元素 - public Set<String>
stringPropertyNames()
:获取所有的键的集合
- public Object
- 和 IO 流结合的方法
- public void
load
(InputStream inStream) - public void
load
(Reader reader):把键值对形式的文本文件内容加载到集合中 - public void
store
(OutputStream out, String comments) - public void
store
(Writer writer,String comments
):把集合中的数据存储到文本文件中,comments是配置描述。
- public void
public class PropertiesDemo {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
// 添加元素
prop.put("it002", "hello");
prop.put("it001", "world");
prop.put("it003", "java");
// 遍历集合
Set<Object> set = prop.keySet();
for (Object key : set) {
Object value = prop.get(key);
System.out.println(key + "---" + value);
}
System.out.println("");
// 添加元素
prop.setProperty("it003", "30"); //方法内部调用 put(key, value);
prop.setProperty("it004", "40");
prop.setProperty("it005", "50");
// public Set<String> stringPropertyNames():获取所有的键的集合
Set<String> set2 = prop.stringPropertyNames();
for (String key : set2) {
String value = prop.getProperty(key);
System.out.println(key + "+++" + value);
}
myStore();
myLoad();
}
private static void myStore() throws IOException {
// 创建集合对象
Properties prop = new Properties();
prop.setProperty("孙悟空", "27");
prop.setProperty("猪八戒", "30");
prop.setProperty("唐僧", "18");
// public void store(Writer writer,String comments):把集合中的数据存储到文件
Writer w = new FileWriter("E:\\demo\\1.txt");
prop.store(w, "西游记");
w.close();
}
private static void myLoad() throws IOException {
Properties prop = new Properties();
// public void load(Reader reader):把文件中的数据读取到集合中
// 注意:这个文件的数据必须是键值对形式
Reader r = new FileReader("E:\\demo\\1.txt");
prop.load(r);
r.close();
System.out.println("prop:" + prop);
}
}
输出:
it003---java
it002---hello
it001---world
it005+++50
it004+++40
it003+++30
it002+++hello
it001+++world
prop:{孙悟空=27, 唐僧=18, 猪八戒=30}
文件E:\demo\1.txt内容:
#\u897F\u6E38\u8BB0
#Mon Jun 07 00:44:14 CST 2021
孙悟空=27
唐僧=18
猪八戒=30
从输出看出,key(it003) 的值被覆盖,因为setProperty(key, value)
内部调用就是 put(key, value)
。
文件内容第一行,中文显示有问题,即使使用转换流Writer w = new OutputStreamWriter(new FileOutputStream("E:\\demo\\1.txt"), "utf-8");
也不行。查看源码,发现有特殊处理…
而且,store()
方法第一个参数是文本内容的编码,只会影响写入文件内容编码,并不会影响写入文件第一行内容。
应用:判断配置文件是否有“lisi”这样的键存在,如果有就改变其实为”100”
public class PropertiesDemo {
public static void main(String[] args) throws IOException {
// 把文件中的数据加载到集合中
Properties prop = new Properties();
Reader r = new FileReader("user.txt");
prop.load(r);
r.close();
// 遍历集合,获取得到每一个键
Set<String> set = prop.stringPropertyNames();
for (String key : set) {
// 判断键是否有为"lisi"的,如果有就修改其值为"100"
if ("lisi".equals(key)) {
prop.setProperty(key, "100");
break;
}
}
// 把集合中的数据重新存储到文件中
Writer w = new FileWriter("user.txt");
prop.store(w, null);
w.close();
}
}
小结
- 读取文本文件,使用字符缓冲流
BufferedReader.readLine
,每次读取一行。 - 写入文本文件,使用
PrintStream
可以传参File、String、OutputStream,可以指定字符集,可以指定是否自动刷新,还可以写入任意类型数据。 - 读取图片、视频,使用字节流
BufferedInputStream
、BufferedOutputStream
。
注:使用字节流读取,一定要用字节流写入;使用字符流读取,一定要用字符流写入。
NIO (Non-blocking I/O)
NIO(Non-blocking I/O)
,是一种同步非阻塞的I/O模型,也是I/O多路复用的基础。已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
组成
NIO主要有三大核心部分:Channel(通道)
,Buffer(缓冲区)
, Selector
。传统IO基于字节流和字符流进行操作,而NIO基于 Channel 和 Buffer(缓冲区) 进行操作。
NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。
- Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。它不能前后移动流中的数据。
- NIO的数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。
阻塞性
- IO的各种流是阻塞的。这意味着,当一个线程调用
read()
或write()
时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 - NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,直至数据变得可以读取之前,所以该线程可以继续做其他的事情。 非阻塞写也是如此。
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
public class NIODemo {
public static void main(String[] args) throws IOException {
// public static long copy(Path source,OutputStream out)
// Files.copy(Paths.get("ByteArrayStreamDemo.java"), new
// FileOutputStream(
// "Copy.java"));
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("world");
array.add("java");
Files.write(Paths.get("E:\\demo\\1.txt"), array, Charset.forName("GBK"));
Files.copy(Paths.get("E:\\demo\\1.txt"),new FileOutputStream("E:\\demo\\2.txt"));
}
}
文件E:\demo\1.txt写入,文件E:\demo\2.txt复制成功。