[Java基础]6. 输入/输出(IO)
文章目录
Java的IO通过java.io包下的类和接口来支持,在java.io包下主要包括输入流、输出流两种IO流,每种输入、输出流又可分为字节流和字符流两大类。其中字节流以字节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出操作。除此之外Java的IO流使用了一种装饰器设计模式,它将IO流分成底层节点流和上层处理流,其中节点流用于和底层的物理存储节点直接关联(不同的物理节点获取节点流的方式可能存在一定的差异),但程序可以把不同的物理节点流包装成统一的处理流,从而允许程序使用统一的输入、输出代码来读取不同的物理存储节点的资源。
一、File类
File类是java.io包下代表与平台无关的文件和目录的类。在程序中操作文件和目录,都可以通过File类来完成。需要注意的是,不管是文件还是目录都是使用File来操作的,File能新建、删除、重命名文件和目录,但是File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
构造器
构造器 | 说明 |
---|---|
File(String pathname) |
|
File(String parent, String child) |
|
File(File parent, String child) |
|
File(URI uri) |
import java.io.File;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
// windows的路径 D:\目录\目录\ 由于\为转义字符 应该写为 D:\\目录\\目录\\
// linux或者unix的路径为 /Home/.....
// 在Java支持将/当作平台无关的分隔符。
System.out.println("与系统有关的路径分隔符(String)‐‐>"+File.pathSeparator
System.out.println("与系统有关的路径分隔符(char)‐‐>"+File.pathSeparatorChar
System.out.println("与系统有关的默认名称分隔符(String)‐‐>"+File.separator
System.out.println("与系统有关的默认名称分隔符(char)‐‐>"+File.separatorChar
// 访问文件名相关方法
File file = new File("D:/Teaching/2017 Java");
System.out.println("文件(目录)名称:" + file.getName());
System.out.println("文件(目录)路径:" + file.getPath());
System.out.println("文件(目录)绝对路径:" + file.getAbsolutePath());
System.out.println("文件(目录)的父目录:" + file.getParent());
if (file.isDirectory()) {
String[] strings = file.list();// 获取子文件和子目录
System.out.println("当前目录下的子文件和子目录");
for (String string : strings) {
System.out.print(string+" , ");
}
}
//创建目录
System.out.println("‐‐‐‐‐‐‐创建目录‐‐‐‐‐‐‐‐‐‐‐");
File fileDir=new File("E:/z/z");
if (!fileDir.exists() || !file.isDirectory()) {
System.out.println(fileDir.mkdir());
}
// 创建文件
System.out.println("‐‐‐‐‐‐‐创建文件‐‐‐‐‐‐‐‐‐‐‐");
File file2 = new File("E:/z/z/123.txt");
System.out.println("文件是否存在" + file2.exists());
System.out.println(file2.createNewFile());
// 读写执行
System.out.println("是否可写:" + file2.canWrite());
System.out.println("是否可读:" + file2.canRead());
System.out.println("是否可执行:" + file2.canExecute());
System.out.println("是否是目录:" + file2.isDirectory());
System.out.println("是否可文件:" + file2.isFile());
System.out.println("是否绝对路径:" + file2.isAbsolute());
System.out.println("是否隐藏:" + file2.isHidden());
// ‐‐‐‐创建一个文件
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
File file3 = new File("E:/z/z/test.txt");
System.out.println("创建文件:" + file3.createNewFile());
System.out.println("文件存在:" + file3.exists());
System.out.println("删除文件:" + file3.delete());
System.out.println("文件存在:" + file3.exists());
System.out.println("创建文件:" + file3.createNewFile());
System.out.println("文件大小:" + file3.length());
System.out.println("文件最后修改时间:" + file3.lastModified());
file3.renameTo(new File("E:/z/test.txt"));// 重命名
System.out.println("‐‐‐‐‐‐‐‐deleteOnExit‐‐‐‐‐‐‐‐‐‐‐‐");
File file4 = new File("E:/z/test2.txt");
System.out.println("创建文件file4:" + file4.createNewFile());
file4.deleteOnExit();//程序退出时删除文件
}
}
遍历目录
通过递归 和 File类打印目录的所有子目录和子文件
import java.io.File;
public class FileDemo2 {
public static void main(String[] args) {
File file=new File("D:/Document");
printDirectory(file,0);
}
public static void printDirectory(File file,int count){
if (file.exists()) {
StringBuffer buffer = new StringBuffer();
if (count!=0) {
buffer.append("|");
}
for (int i = 0; i < count; i++) {
buffer.append("‐‐");
}
if (file.isDirectory()) {
System.out.println(buffer.toString()+"["+file.getName()+"]");
File[] files=file.listFiles();
for (File file2 : files) {
printDirectory(file2,count+1);
}
}
else{
System.out.println(buffer.toString()+">"+file.getName());
}
}
}
}
二、Java IO流的分类与概念模型
Java的IO流是实现输入/输出的基础,它可以方便地实现数据的输入/输出操作,在Java中把不同的输入/输出源(键盘、文件、网络连接等)抽象表述为“流”(stream),通过流的方式允许Java程序使用相同的方式来访问不同的输入输出源。 stream是从起源(source)到接收(sink)的有序数据。
Java把所有传统的流类型(类或抽象类)都放在java.io包中,用以实现输入输出功能。
流的分类
-
输入流和输出流
- 输入流:只能从中读取数据,而不能向其写入数据。由
InputStream
和Reader
作为基类 - 输出流:只能向其写入数据,而不能从中读取数据。由
OutputStream
和Writer
作为基类
- 输入流:只能从中读取数据,而不能向其写入数据。由
-
字节流和字符流
- 字节流操作的数据单元是8位的字节,由
InputStream
和OutputStream
作为基类。 - 字符流操作的数据单元是16位的字符,由
Reader
和Writer
作为基类。
- 字节流操作的数据单元是8位的字节,由
-
节点流和处理流
- 节点流:可以从向一个特定的IO设备(如磁盘、网络)读/写数据的流。也被称为低级流。
- 处理流:用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能。也称为高级流。
Java的IO流的40多个类都是从如下4个抽象基类派生的。
- InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流
- OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流
三、字节流和字符流
字节流和字符流的操作方式几乎完全一样,区别只是操作的数据单元不同。
InputStream类的方法
方法 | 返回值 | 说明 |
---|---|---|
close() |
void | 关闭此输入流并释放与该流关联的所有系统资源。 |
read() |
abstract int | 从输入流中读取数据的下一个字节。 |
read(byte[] b) |
int | 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b 中。 |
read(byte[] b, int off, int len) |
int | 将输入流中最多 len 个数据字节读入 byte 数组。 |
available() |
int | 返回此输入流下一个方法调用可以不受阻塞地从 此输入流读取(或跳过)的估计字节数。 |
reset() |
void | 将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。 |
skip(long n) |
long | 跳过和丢弃此输入流中数据的 n 个字节。 |
mark(int readlimit) |
void | 在此输入流中标记当前的位置。 |
markSupported() |
boolean | 测试此输入流是否支持 mark 和 reset 方法。 |
Reader类的方法
方法名 | 返回值 | 说明 |
---|---|---|
close() |
abstract void | 关闭该流并释放与之关联的所有资源。 |
read() |
int | 读取单个字符。 |
read(char[] cbuf) |
int | 将字符读入数组。 |
read(char[] cbuf, int off, int len) |
abstract int | 将字符读入数组的某一部分。 |
read(CharBuffer target) |
int | 试图将字符读入指定的字符缓冲区。 |
ready() |
boolean | 判断是否准备读取此流。 |
reset() |
void | 重置该流。 |
skip(long n) |
long | 跳过字符。 |
mark(int readAheadLimit) |
void | 标记流中的当前位置。 |
markSupported() |
boolean | 判断此流是否支持 mark() 操作。 |
InputStream和Reader的方法基本一致,只是InputStream读取的是字节,使用的参数是byte数组(byte[]),而Reader读取的是字符,使用的参数是char数组(char[])。
InputStream和Reader都是抽象类,本身不能创建实例,但它们分别有一个子类用于读取文件的输入流:FileInputStream和 FileReader,它们都是节点流需要直接和指定文件关联。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
public class InputStreamDemo {
public static void main(String[] args) {
try {
//创建流
InputStream in = new FileInputStream("E:/z/BaseDataType.java");
//创建缓存数组
byte[] bs=new byte[1026];
//存放读取的字节数
int count=0;
//读取数据
while ((count = in.read(bs, 0, bs.length)) != ‐1) {
System.out.println(new String(bs,0,count,Charset.forName("utf‐8");
}
// 用完流,要记得关闭
in.close();
} catch (FileNotFoundException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
}
}
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class ReaderDemo {
public static void main(String[] args) {
//创建字符流
try {
Reader reader=new FileReader("E:/z/BaseDataType.java");
char[] cs=new char[1024];
int count=0;
while((count=reader.read(cs, 0, cs.length))!=‐1){
System.out.println(new String(cs, 0, count));
}
} catch (FileNotFoundException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
}
}
OutputStream类的方法
方法 | 说明 |
---|---|
void close() |
关闭此输出流并释放与此流有关的所有系统资源。 |
void flush() |
刷新此输出流并强制写出所有缓冲的输出字节。 |
void write(byte[] b) |
将 b.length 个字节从指定的 byte 数组写入此输出流。 |
void write(byte[] b, int off, int len) |
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 |
abstract void write(int b) |
将指定的字节写入此输出流。 |
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class OutputStreamDemo {
public static void main(String[] args) {
String str= "OutputStream和Write是所有输出流的基类";
try {
//创建一个输出流
OutputStream out = new FileOutputStream("E:/z/OutputStreamDemo.txt");
//将str的byte数组数据写入到输出流
out.write(str.getBytes());
out.flush();
//关闭输出流
out.close();
} catch (FileNotFoundException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
}
}
Writer类的方法
方法 | 说明 | 返回值 |
---|---|---|
append(char c) |
将指定字符添加到此 writer。 | Writer |
`append(CharSequence csq) | 将指定字符序列添加到此 writer。 | Writer |
append(CharSequence csq, int start, int end) |
将指定字符序列的子序列添加 | Writer |
close() |
关闭此流,但要先刷新它。 | abstract void |
flush() |
刷新该流的缓冲。 | abstract void |
write(char[] cbuf) |
写入字符数组。 | void |
write(char[] cbuf, int off, int len) |
写入字符数组的某一部分。 | abstract void |
write(int c) |
写入单个字符。 | void |
write(String str) |
写入字符串。 | void |
write(String str, int off, int len) |
写入字符串的某一部分。 | void |
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriterDemo {
public static void main(String[] args) {
String str="OutputStream和Write是所有输出流的基类";
try {
Writer writer=new FileWriter("E:/z/WriterDemo.txt");
writer.write(str);
//相比OutputStream,Writer多了append方法
writer.append("\r\n");
writer.append('A');
writer.flush();
writer.close();
} catch (IOException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
}
}
四、处理流
处理流可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入/输出方法,让程序员只需关心高级流的操作。
使用处理流时的典型思路是,使用处理流来包装节点流,程序通过处理流来执行输入输出功能,让节点流与底层的IO设备、文件交互。
实际识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流就一定是处理流;而所有节点流都是直接以物理IO节点作为构造器参数的。
使用处理流的优势:
- 对开发人员来说,使用处理流进行输入/输出操作更简单;
- 使用处理流的执行效率更高。
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
Writer writer = new FileWriter("E:/z/PrintStreamDemo.txt");
PrintWriter printWriter = new PrintWriter(writer);
String str = "处理流可以隐藏底层设备上节点流的差异";
String str2 = "使用处理流时的典型思路";
printWriter.println(str);
printWriter.println(str2);
//在使用处理流包装了底层节点流之后,关闭输入/输出流资源时,只要关闭最上层的处理流,关闭最上层的处理流时,系统会自动关闭被该处理流包装的节点流。
printWriter.close();
}
}
五、转换流
InputStreamReader
将字节输入流转换成字符输入流, OutputStreamWriter
将字节输出流转换成字符输出流。
六、推回输入流
使用IO里面的推回输入流读取数据则可以把数据给推回到输入流的缓冲区中。推回输入流主要为两个类:PushbackInputStream
和 PushbackReader
。
下面使用字符推回输入流PushbackReader来查找程序中"//字符型 char" 字符串,找到之后只是打印目标串之前的内容。
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;
public class PushbackReaderDemo {
public static void main(String[] args) throws IOException {
// 创建字符输入流
Reader reader1 = new FileReader("E:/z/BaseDataType.java");
// 创建推回输入流 指定推回缓冲区大小为64,
//如果不指定,默认缓冲区大小为1
PushbackReader reader = new PushbackReader(reader1, 64);
char[] cs = new char[32];// 临时数组
int count = 0;// 读取的字符数
String strLast = "";// 记录上次读取的字符串
while ((count = reader.read(cs, 0, cs.length)) != ‐1) {
// 本次读取内容
String str = new String(cs, 0, count);
// 上次+本次读取内容 ‐‐ 避免 要查找的字符串被截断
String StrContent = strLast + str;
// 查找目标字符串
// 目标出现位置
int targetIndex = StrContent.indexOf("//字符型 char");
if (targetIndex > ‐1) {
// 找到目标字符串
// 将本次内容和上次内容一起推回缓冲区
// 推回缓冲区的内容大小不能超过缓冲区的大小
reader.unread(StrContent.toCharArray());
// 判断 targetIndex 是否 > 32(临时数组大小)
if (targetIndex > 32) {
cs = new char[targetIndex];
}
// 再次读取指定长度的内容(就是目标字符串之前的内容)
reader.read(cs, 0, targetIndex);
str = new String(cs, 0, targetIndex);
System.out.print(str);
break;
} else {
// 为找到目标字符串
// 直接输出
System.out.print(strLast);
strLast = str;
}
}
}
}
七、访问数组
ByteArrayOutputStream
和ByteArrayInputStream
类是在创建它的实例时,程序内部创建一个byte型数组的缓冲区,然后利用ByteArrayOutputStream和ByteArrayInputStream的实例向数组中写入或读出byte型数据。在网络传输中我们往往要传输很多变量,我们可以利用ByteArrayOutputStream把所有的变量收集到一起,然后一次性把数据发送出去。
- ByteArrayOutputStream:可以捕获内存缓冲区的数据,转换成字节数组。
- ByteArrayInputStream:可以将字节数组转化为输入流
- CharArrayReader、CharArrayWriter的使用与ByteArrayOutputStream、ByteArrayInputStream差不多。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
public class ArrayStreamDemo {
public static void main(String[] args) throws IOException {
String str1="访问 byte[] 和 char[]的IO流 ;";
String str2="ByteArrayInputStream ByteArrayOutputStream;";
String str3="CharArrayReader CharArrayWriter;";
//ByteArrayInputStream ByteArrayOutputStream
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(str1.getBytes());
out.write(str2.getBytes());
out.write(str3.getBytes());
//转换为byte数组
byte[] buff = out.toByteArray();
ByteArrayInputStream in = new ByteArrayInputStream(buff);
byte[] bs=new byte[1024];
int count=0;
while ((count = in.read(bs,0,bs.length)) != ‐1) {
System.out.println(new String(bs,0,count));
}
//ByteArrayInputStream ByteArrayOutputStream 不需要关闭流
// out.close();
// in.close();
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
//CharArrayReader CharArrayWriter
CharArrayWriter writer=new CharArrayWriter();
writer.write(str1);
writer.write(str2);
writer.write(str3);
char[] chars=writer.toCharArray();
CharArrayReader reader=new CharArrayReader(chars);
char[] cs=new char[200];
while((count=reader.read(cs, 0, cs.length))!=‐1){
System.out.println(new String(cs,0,count));
}
//CharArrayReader CharArrayWriter 不需要关闭流
}
}
八、缓冲流
在读写数据时,让数据在缓冲区能减少系统实际对原始数据来源的存取次数,因为一次能做多个数据单位的操作。相较而言,对于直接从文件读取数据或将数据写入文件,比起缓冲区的读写要慢多了。所以使用缓冲区的流,一般都会比没有缓冲区的流效率更高。拥有缓冲区的流被称为缓冲流,包括BufferedInputStream、BufferedOutputStream类和BufferedReader、BufferedWriter类。缓冲流把数据从原始流成块读入或把数据积累到一个大数据块后再成批写出,通过减少通过资源的读写次数来加快程序的执行。
BufferedReader类
BufferedReader是Reader的子类,可以Reader的方法。从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。下面是BufferedReader的够构造器和特有的方法。
构造器/方法名 | 作用 |
---|---|
BufferedReader(Reader in) |
创建一个使用默认大小输出缓冲区的缓冲字符输入流对象 |
BufferedReader(Reader out,int sz) |
用指定大小 |
readLine() |
读取一个文本行 |
BufferedWriterr类
BufferedWriter类是Writer的子类。
构造器/方法名 | 作用 |
---|---|
BufferedWriter(Writer out) |
创建一个使用默认大小输出缓冲区的缓冲字符输出流对象 |
BufferedWriter(Writer out,int sz) |
用指定大小 |
newLine() |
写入一个行分隔符 |
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
public class BufferedDemo {
public static void main(String[] args) throws IOException {
//BufferedReader
Reader in=new FileReader("E:/z/BaseDataType.java");
BufferedReader reader=new BufferedReader(in);
String str="";
//使用readLine() 比较方便的读取一行
while (null != (str = reader.readLine())) {
System.out.println(str);
}
reader.close();//关闭流
//BufferedWriter
Writer out=new FileWriter("E:/z/BufferedWriter.txt");
BufferedWriter writer=new BufferedWriter(out);
writer.write("BufferedWriter Line 1");
writer.newLine();//写入一个行分隔符。
writer.write("BufferedWriter Line 2");
writer.newLine();//写入一个行分隔符。
writer.write("BufferedWriter Line 3");
writer.flush();
writer.close();//关闭流
}
}
九、访问字符串
StringReader
:用来将字符串转换成字符输入流。然后使用字符输入流提供的方式进行操作,也可以提供给其他高级字符输入流来使用。如可以将该字符输入流提供给BufferedReader输入流使用。
StringWriter
:在内存中缓存读取到的所有字符串,然后使用通过toString方法一次性全部输出字符串。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
public class StringReaderWriterDemo {
public static void main(String[] args) throws IOException {
//==StringReader
//模拟从某个地方获取到的大量字符串数据
String str = "StringReader:用来将字符串转换成字符输入流。" +
"然后使用字符输入流提供的方式进行操作,也可以提供给" +
"其他高级字符输入流来使用。如可以将该字符输入流提供给" +
"BufferedReader输入流使用。\r\nStringWriter:" +
"在内存中缓存读取到的所有字符串,然后使用通" +
"过toString方法一次性全部输出字符串。";
//把字符串转为输入流
StringReader in = new StringReader(str);
//通过BufferedReader访问字符串
BufferedReader reader = new BufferedReader(in);
String strT="";
while ((strT=reader.readLine())!=null) {
System.out.println(strT);
}
reader.close();//关闭流
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
//==StringWriter
//创建StringWriter实例
StringWriter writer=new StringWriter();
//通过Reader读取文件内容
Reader reader1 = new FileReader("E:/z/BaseDataType.java");
char[] cs=new char[50];
int count=0;
while ((count=reader1.read(cs, 0, cs.length))!=‐1) {
//把读取的内容写入到StringWriter对象
writer.write(cs, 0, count);
}
reader1.close();//关闭输入流
//一次性将字符串取出
String strConten=writer.toString();
writer.close();//关闭StringWriter
System.out.println(strConten);
}
}
十、访问管道(管道流)
管道流是用来在多个线程之间进行信息传递的Java流,被号称是最难使用的流,被使用的频率比较低
它提供了多线程间信息传输的一种有效手段。
管道流包括四个类PipedOutputStream/PipedWriter和PipedInputStream/PipedReader。
其中PipedOutputStream/PipedWriter是写入者/生产者/发送者;
PipedInputStream/PipedReader是读取者/消费者/接收者。
在使用管道流之前,需要注意以下要点:
- 管道流仅用于多个线程之间传递信息,若用在同一个线程中可能会造成死锁;
- 管道流的输入输出是成对的,一个输出流只能对应一个输入流,使用构造函数或者connect函数进行连接;
- 一对管道流包含一个缓冲区,其默认值为1024个字节,若要改变缓冲区大小,可以使用带有参数的构造器;
- 管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时,写操作阻塞;
- 管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;
- 管道流的读取方法与普通流不同,只有输出流正确close时,输出流才能读到-1值。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
public class PipedStreamDemo {
public static void main(String[] args) throws IOException {
PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader();
//连接两个线程的管道流
writer.connect(reader);
ThreadWriter one = new ThreadWriter(writer);
ThreadReader two = new ThreadReader(reader);
one.start();
two.start();
}
}
class ThreadWriter extends Thread{
//声明管道流
private PipedWriter writer;
//构造器
public ThreadWriter(PipedWriter writer) {
super();
this.writer = writer;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
writer.write("这是ThreadWriter! count:"+i+"\r\n");
writer.flush();
}
writer.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
class ThreadReader extends Thread{
//声明管道流 发送着和接收者
private PipedReader reader;
//构造器
public ThreadReader(PipedReader reader) {
super();
this.reader = reader;
}
@Override
public void run() {
try {
String str="";
BufferedReader bReader=new BufferedReader(reader);
while ((str=bReader.readLine())!=null) {
System.out.println("ThreadReader:"+str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
十一、RandomAccessFile类
RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法 来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。
RandomAccessFile支持“随机访问”的方式,程序可以直接跳转到文件的任意地方来读写数据。
RandomAccessFile四种访问模式:
r
:以只读方式打开指定文件。如果试图对该RandomAccessFile执行写入方法,都将抛出 IOException 异常。rw
:以读、写方式打开指定文件。如果该文件尚不存在,则尝试创建该文件。rws
:以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。rwd
: 以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容的每个更新都同步写入到底层存储设备。
元数据是文件的附加属性,如文件大小、创建时间、所有者等信息。
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
demo1();
// demo2();
// demo3();
// demo4();
}
public static void demo1() throws IOException {
// 实例化RandomAccessFile类,指定只读模式
RandomAccessFile accessFile = new RandomAccessFile("E:\\z\\BaseDataType.java", "r");
// 调到指定位置
accessFile.seek(24);
// 读取文件
byte[] bytes = new byte[1024];
int count = 0;
while (‐1 != (count = accessFile.read(bytes, 0, bytes.length))) {
System.out.println(new String(bytes, 0, count));
}
// 关闭流
accessFile.close();
}
public static void demo2() throws IOException {
// 读写模式
RandomAccessFile accessFile = new RandomAccessFile("E:\\z\\BaseDataType.java", "rw");
// 跳到文件末尾位置
accessFile.seek(accessFile.length());
accessFile.write("//在末尾追加的注释".getBytes(Charset.forName("utf‐8")));
// 关闭流
accessFile.close();
}
/**
* RandomAccessFile依然不能向文件的指定位置插入内容,
* 如果直接将文件记录指针移动到中间某位置后开始输出,
* 则新输出的内容会覆盖文件中原有的内容。
*
* 如果需要向指定位置插入内容,
* 程序需要先把插入点后面的内容读入缓冲区,
* 等把需要插入的数据写入文件后,
* 再将缓冲区的内容追加到文件后面。
*/
public static void demo3() throws IOException {
RandomAccessFile accessFile = new RandomAccessFile("E:\\z\\BaseDataType.java", "rw");
// 调到指定位置
accessFile.seek(766);
String str = "//这串字符会覆盖后面的字符串";
accessFile.write(str.getBytes(Charset.forName("utf‐8")));
accessFile.close();
}
public static void demo4() throws IOException {
//存放读取到的 插入点后面的内容
ByteArrayOutputStream arrayout = new ByteArrayOutputStream();
RandomAccessFile accessFile = new RandomAccessFile("E:\\z\\BaseDataType.java", "rw");
// 调到指定位置
accessFile.seek(766);
// 读取指定位置后的内容到 arrayout
byte[] bytes = new byte[1024];
int count = 0;
while (‐1 != (count = accessFile.read(bytes, 0, bytes.length))) {
arrayout.write(bytes, 0, count);
}
// 调到指定位置
accessFile.seek(766);
// 插入想插入的内容
accessFile.write("//插入的字符串".getBytes(Charset.forName("utf‐8")));
// 读取arrayout 里面的内容,追加会源文件
accessFile.write(arrayout.toByteArray());
//关闭流
accessFile.close();
}
}
十二、对象序列化
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流(无论是从磁盘中获取的,还是通过网络获取的),都可以将这种二进制流恢复成原来的Java对象。
对象的序列化(Serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该Java对象如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的(serializable)。
为了让某个类是可序列化的,该类必须实现如下两个接口之一:Serializable(常用)、ExternalizableJava。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ObjectStream {
public static void main(String[] args) throws Exception {
//‐‐‐‐‐‐‐‐‐‐序列化过程‐‐‐‐‐‐‐‐‐
System.out.println("‐‐‐‐‐‐‐‐‐‐序列化过程‐‐‐‐‐‐‐‐‐");
//创建实例
Person person = new Person("小明", 20);
//实例化 FileOutputStream
FileOutputStream out = new FileOutputStream("E:\\z\\person.txt");
//实例化 ObjectOutputStream
ObjectOutputStream outputStream = new ObjectOutputStream(out);
// 周用ObjectOutputStream对象的writeObject()方法输出可序列化对象
outputStream.writeObject(person);
//关闭 ObjectOutputStream
outputStream.close();
//‐‐‐‐‐‐‐‐‐‐反序列化过程‐‐‐‐‐‐‐‐‐‐
System.out.println("‐‐‐‐‐‐‐‐‐‐反序列化过程‐‐‐‐‐‐‐‐‐‐");
//实例化 FileInputStream
FileInputStream in = new FileInputStream("E:\\z\\person.txt");
//实例化 ObjectInputStream
ObjectInputStream inputStream = new ObjectInputStream(in);
/**
* 必须指出的是,反序列化读取的仅仅是Java对象的数据,
* 而不是Java类, 因此采用反序列化恢复Java对象时,
* 必须提供该Java对象所属类的class文件,
* 否则将会引发ClassNotFoundException异常。
* 反序列化无须通过构造器来初始化Java对象。
*/
//readObject() 可以反序列化对象
Person person2 = (com.gx.iodemo.Person) inputStream.readObject();
//关闭 ObjectInputStream
inputStream.close();
System.out.println("name="+person2.getName());
System.out.println("age="+person2.getAge());
}
}
class Person implements Serializable {
private static final long serialVersionUID = ‐3138823756540735938L;
private String name;
private int age;
public Person() {
System.out.println("无参构造器");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造器");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
十三、Properties类
Properties 类是Java中操作配置文件的类
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.util.Properties;
public class PropertiesDemo {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
// 加载配置文件
// 1、字节流(输入) Reader
FileInputStream in = new FileInputStream("./src/test.properties");
properties.load(in);
// 2、字符流(输入) Reader
Reader reader = new FileReader(new File("./src/test.properties"));
properties.load(reader);
reader.close();
//读取配置内容
System.out.println("name:"+properties.getProperty("name"));
//指定默认值,当无法找到key时返回指定的默认值
//当 key不存在时,不指定默认值输出为null,指定默认值后输出默认值
System.out.println("name:"+properties.getProperty("name","233333"));
//设置配置的值
//当key存在时,修改值;key不存在时,添加
properties.setProperty("name","西米");
properties.setProperty("height", String.valueOf(170));
properties.remove("height");
// 配置输出
//1.输出到控制台
properties.list(System.out);
//2、输出到文件
PrintStream outputStream=new PrintStream(new File("./src/test.properties"
properties.store(outputStream,"这是注释");
}
}