——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
Java对数据的操作是通过流的方式,操作流的对象都在IO包中,所以IO流(InputOutputStream)可以用来处理设备之间的数据传输。
处理数据时,一定要先明确数据源,与数据目的(数据汇)。数据源可以是文件,键盘。数据目的可以是文件,显示器或者其他设备。而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理,转换处理等。
流按操作数据分类可分为:字节流,字符流。
流按流向分类可分为:输入流,输出流。
字节流的抽象基类:InputStream,OutputStream。
字符流的抽象基类:Reader,Writer。
由这四个类派生出来的子类,名称都是以其父类名作为子类名的后缀。如:InputStream的子类FileInputStream;Reader的子类FileReader。
导入IO包中的类。
进行IO异常处理。
在finally中对流进行关闭。
示例代码:
/* 导入包中的类 */
import java.io.*;
public static void main(String[] args) {
FileReader fw = null;
/* 进行IO异常处理 */
try {
fw = new FileWriter("source.txt");
fw.write("anything");
} catch (IOException e) {
throw new RuntimeException("写入失败");
}
/* 在finally中关闭流资源 */
finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException("关闭写入流失败");
}
}
}
}
(1)必须自行关闭流资源。虽然Java有垃圾回收机制,但是垃圾回收机制只能处理Java虚拟机所产生的内存垃圾,而流所消耗的操作系统资源、硬件、网络资源等外部资源,不在垃圾回收器的处理范围内。
(2)必须处理IO异常。因为你调用的方法向你抛出了异常,你必须选择接受它,或者抛给调用你方法的方法,否则程序会报错。
(1)创建流对象,建立数据存放文件。
(2)调用流对象写入文件,将数据写入流。
(3)关闭流资源,并将流中的数据清空到文件中。
示例代码:
FileWriter fw = new Filewriter("Test.txt");
fw.write("text");
fw.close();
注意:关闭流后,就不能对文件进行读取和写入的操作了。
(1)建立一个流对象,将已存在的一个文件加载进流。
(2)创建一个临时存放数据的数组。
(3)调用流对象的读取方法将流中的数据读入到数组中。
(4)关闭流资源
代码示例:
FileReader fr = new FileReader("Test.txt");
char[] ch = new char[1024];
fr.read(ch);
fr.close();
定义文件路径时,用“\”。
在创建文件时,目录下如果有同名文件将会被覆盖。
在读取文件时,必须保证该文件已存在,否则出异常。
定义数组时,根据内存的进位机制,建议定义大小为1024的整数倍。
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/*
* 需求:复制一个文本文件
*/
public class CopyText {
public static void main(String[] args) {
charCopy();
strCopy();
}
public static void charCopy() { // 按字符读
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("D:\\1.txt");
fw = new FileWriter("D:\\2.txt");
for (int len = 0; (len = fr.read()) != -1; ) {
fw.write(len);
}
} catch (IOException e) {
throw new RuntimeException("读写失败");
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e){
throw new RuntimeException("关闭读取流失败");
}
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException("关闭写入流失败");
}
}
}
}
public static void strCopy() { // 按字符数组读
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("D:\\1.txt");
fw = new FileWriter("D:\\2.txt");
char[] str = new char[1024];
for (int len = 0; (len = fr.read(str)) != -1; ) {
fw.write(str, 0, len);
}
} catch (IOException e) {
throw new RuntimeException("读写失败");
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
throw new RuntimeException("关闭读取流失败");
}
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException("关闭写入流失败");
}
}
}
}
}
字符流缓冲区的出现提高了数据的读写效率。
是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。明确要提高具体流对象的效率。
该缓冲区提供了一个一次读一行的方法readLine(),方便于对文本数据的获取。当返回null时,表示读到文件末尾。该方法返回的时候只返回回车之前的数据内容,并不返回回车符。
该缓冲区还提供了一个跨平台的换行符newLine()。
字符流缓冲区对应类:
BufferedWriter
BufferedReader
注意:
缓冲区要结合流才可以使用。
在流的基础上对流的功能进行了增强。
练习一:
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/*
* 需求:读取文本文件
*/
public class BufferReaderDemo {
public static void main(String[] args) {
FileReader fr;
try {
//创建一个读取流对象和文件相关联
fr = new FileReader("D:\\buf2.txt");
} catch (FileNotFoundException e) {
throw new RuntimeException("找不到文件");
}
//为了提高效率,加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数
BufferedReader bufr = new BufferedReader(fr);
try {
for (String line = null; (line = bufr.readLine()) != null; ) {
System.out.println(line);
}
} catch (IOException e) {
throw new RuntimeException("读取文件失败");
} finally {
try {
bufr.close();
} catch (IOException e) {
throw new RuntimeException("关闭读取流失败");
}
}
}
}
练习二:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
/*
* 需求:创建文本文件并写入数据
*/
public class BufferedWriterDemo {
public static void main(String[] args) {
FileWriter fw;
try {
fw = new FileWriter("D:\\buf2.txt");
} catch (IOException e) {
throw new RuntimeException("创建文件失败");
}
BufferedWriter bufw = new BufferedWriter(fw);
try {
for (int x = 1; x < 5; x++) {
try {
bufw.write("Line" + x);
} catch (IOException e) {
throw new RuntimeException("写入数据失败");
}
try {
bufw.newLine();
} catch (IOException e) {
throw new RuntimeException("插入行失败");
}
try {
bufw.flush();
} catch (IOException e) {
throw new RuntimeException("刷新流失败");
}
}
} finally {
try {
bufw.close();
} catch (IOException e) {
throw new RuntimeException("关闭写入流失败");
}
}
}
}
装饰设计模式是对原有类进行了功能的改变和增强。
装饰类是通过构造方法将已有对象传入,基于已有的功能,并提供增强功能的一个自定义类。
装饰设计模式可提高扩展性。
装饰设计模式比继承要灵活,避免了继承体系臃肿,降低了类与类之间的关系。
装饰类是具备已有对象的功能后,提供更强的功能,所以装饰类和被装饰类通常属于一个体系。
/*
* 假设有一个专门用于读取数据的类MyReader,其子类有MyTextReader,MyMediaReader,MyDataReader。现在要新增一个缓冲功能MyBufferedReader。
*/
class MyBufferedReader {
MyBufferedReader(MyTextReader text) {}
MyBufferedReader(MyMediaReader media) {}
MyBufferedReader(MyDataReader data) {}
}
// 通过继承使每一个子类都具备缓冲功能。这样的继承体系很复杂,并不利于扩展。
class MyBufferedReader extends MyReader {
private MyReader r;
MyBufferedReader(MyReader r) {
this.r = r;
}
}
class MyTextReader {
public static void main(String[] args) {
MyBufferedReader myBuf = new MyBufferedReader(new MyTextReader("*.txt"));
}
}
//单独描述缓冲内容,将需要被缓冲的对象传递给缓冲区。这样的继承体系就变得简单。
代码示例:
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
/*
* 需求:模拟BufferdReader,自定义一个类包含与readLine一致的方法。
*/
class MyBufferedReader extends Reader {
private Reader r;
MyBufferedReader(Reader r) {
this.r = r;
}
public String myReadLine() throws IOException {
StringBuilder sb = new StringBuilder();
for (int ch = 0; (ch = r.read()) != -1; ) { // 如果读到回车就返回字符集,如果没有就添加一个字符
if (ch == '\r') {
continue;
}
if (ch == '\n') {
return sb.toString();
} else {
sb.append((char)ch);
}
}
if (sb.length() != 0) { // 如果在文件的最后一行没有输入回车,也需要显示出该行
return sb.toString();
}
return null;
}
// 复写父类中的读取流方法
public int read(char[] cbuf, int off, int len) throws IOException {
return r.read(cbuf, off, len);
}
// 复写父类中的关闭流方法
public void close() throws IOException {
r.close();
}
}
public class MyBufferedReaderDemo {
public static void main(String[] args) throws IOException {
MyBufferedReader myBuf = null;
try {
myBuf = new MyBufferedReader(new FileReader("D:\\buf.txt"));
for (String line = null;(line = myBuf.myReadLine()) != null; System.out.println(line));
} catch (IOException e) {
throw new RuntimeException("读取数据失败");
} finally {
try {
if (myBuf != null) {
myBuf.close();
}
} catch (IOException e) {
throw new RuntimeException("关闭读取流失败");
}
}
}
}
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
/*
* 需求:模拟一个LineNumberReader类,读取文本文档带行号输出。
*/
class MyLineNumberReader extends MyBufferedReader
{
private int lineNumber;
MyLineNumberReader(Reader r) {
super(r);
}
public String myReadLine() throws IOException {
lineNumber ++;
return super.myReadLine();
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public int getLineNumber() {
return lineNumber;
}
}
public class LineNumberReaderDemo {
public static void main(String[] args) {
FileReader fr;
try {
fr = new FileReader("D:\\1.txt");
} catch (FileNotFoundException e) {
throw new RuntimeException("找不到文件");
}
MyLineNumberReader lnr = new MyLineNumberReader(fr);
lnr.setLineNumber(0);
try {
for (String line = null; (line = lnr.myReadLine()) != null; ) {
System.out.println(lnr.getLineNumber() + ":" + line);
}
} catch (IOException e) {
throw new RuntimeException("读取文件失败");
} finally {
try {
lnr.close();
} catch (IOException e) {
throw new RuntimeException("关闭流失败");
}
}
}
}
字节流不仅可以操作字符,还可以操作其他媒体文件。基本操作与字符流类相同。
字节流创建文件步骤:
创建流对象,建立数据存放文件。
调用流对象写入文件,将数据写入流。
关闭流资源,并将流中的数据清空到文件中。
FileOutputStream fos = new FileOutputStream("fos.txt");
fos.write("abcde".getBytes());
fos.close();
建立一个流对象,将已存在的一个文件加载进流。
创建一个临时存放数据的数组。
调用流对象的读取方法将流中的数据读入到数组中。
关闭流资源
FileInputStream fis = new FileInputStream("Test.txt");
byte[] buf = new char[fis.available()];
fr.read(buf);
fr.close();
int available():返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。
字节流缓冲区提高了字节流的读写效率。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
* 需求:拷贝mp3文件,并比较字节流与缓冲字节流操作文件的效率。
*/
public class CopyMp3 {
public static void main(String[] args) {
long start = System.currentTimeMillis();
noBuffered();
long end = System.currentTimeMillis();
System.out.println("未加入缓冲区: " + (end - start) + "毫秒");
start = System.currentTimeMillis();
buffered();
end = System.currentTimeMillis();
System.out.println("加入缓冲区: " + (end - start) + "毫秒");
}
//未加入缓冲区
public static void noBuffered() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("d:\\1.mp3");
fos = new FileOutputStream("d:\\2.mp3");
byte[] buf = new byte[fis.available()]; //FileInputStream的available方法通过文件描述符获取文件的总大小
int len = 0;
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
} catch (IOException e) {
throw new RuntimeException("复制文件失败");
} finally {
try {
fos.close();
} catch (IOException e){
throw new RuntimeException("关闭输出流失败");
}
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException("关闭写入流失败");
}
}
}
//加入缓冲区
public static void buffered() {
BufferedInputStream bufis = null;
BufferedOutputStream bufos = null;
try {
bufis = new BufferedInputStream(new FileInputStream("d:\\3.mp3"));
bufos = new BufferedOutputStream(new FileOutputStream("d:\\4.mp3"));
for (int len = 0; (len = bufis.read()) != -1; ) {
bufos.write(len);
}
} catch (IOException e) {
throw new RuntimeException("复制文件失败");
} finally {
try {
bufos.close();
} catch (IOException e) {
throw new RuntimeException("关闭输出流失败");
} catch (NullPointerException e) {
throw new RuntimeException("找不到文件");
}
try {
bufis.close();
} catch (IOException e) {
throw new RuntimeException("关闭写入流失败");
}
}
}
}
转换流方便了字符流与字节流之间的操作,是字符流与字节流之间的桥梁。
转换流的应用:当字节流中的数据都是字符时,转成字符流操作更高效。
转换流抽象基类:
InputStreamReader
OutputStreamWriter
字符流对象转成字节流对象:
// 获取键盘录入对象
InputStream in = System.in;
// 将字符流对象转成字节流对象
InputStreamReader isr = new InputStreamReader(in);
// 为了提高效率,加入缓冲流
BufferedReader bufr = new BufferedReader(isr);
/* 获取键盘录入的常见写法 */
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
字节流对象转成字符流对象:
// 把对象输出到显示器
OutputStream out = System.out;
// 将字节流对象转成字符流对象
OutputStreamWriter osw = new OutputStreamWriter(out);
// 为了提高效率,加入缓冲流
BufferedWriter bufw = new BufferedWriter(osw);
/* 输出到显示器的常见写法 */
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
System类中的字段是in,out。它们各代表了系统标准的输入和输出设备。默认输入设备是键盘,输出设备是显示器。例如,获取键盘录入数据,然后将数据流向显示器,那么显示器就是目的地。
System.in的类型是InputStream。
System.out的类型是PrintStream。OutputStream中FilterOutputStream的子类。
改变默认设备:通过System类的setIn,setOut方法对默认设备进行改变。
System.setIn(new FileInputStream("1.txt")); //将源改成文件1.txt。
System.setOut(new FileOutputStream(“2.txt”)); //将目的改成文件2.txt
标准输入输出形式:
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
代码示例:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
/*
* 需求:在控制台中输入字符,输出到文本文件中
*/
public class TransStreamDemo {
public static void main(String args[]) {
try {
System.setOut(new PrintStream("d:\\b.txt"));
} catch (FileNotFoundException e) {
throw new RuntimeException("找不到文件");
}
BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bfw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
try {
while ((line = bfr.readLine()) != null) {
if (!line.endsWith("over")) { // 输入over结束循环
bfw.write(line);
bfw.newLine();
bfw.flush();
} else {
break;
}
}
} catch (IOException e) {
throw new RuntimeException("操作文件失败");
} finally {
try {
bfr.close();
} catch(IOException e) {
throw new RuntimeException("关闭读取流失败");
}
try {
bfw.close();
} catch(IOException e) {
throw new RuntimeException("关闭写入流失败");
}
}
}
}
流对象:其实很简单,就是读取和写入。但是因为功能的不同,流的体系中提供了N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。
(1)明确源和目的。
(2)操作的数据是否是纯文本。
(3)明确使用哪个对象。
(4)是否需要提高效率。
File类用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作。
File对象可以作为参数传递给流的构造函数。
File类的常用方法:
boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。
boolean mkdir():创建文件夹。
boolean mkdirs():创建多级文件夹。
boolean delete():删除文件。如果文件正在被使用,在删除失败,返回false。
void deleteOnExit():在程序退出时删除指定文件。
boolean exists():测试此抽象路径名表示的文件或目录是否存在。
boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。
boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute(): 测试此抽象路径名是否为绝对路径名。
String getName():返回由此抽象路径名表示的文件或目录的名称。
String getPath():将此抽象路径名转换为一个路径名字符串。
String getParent():返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。
代码示例:
import java.io.File;
import java.io.FilenameFilter;
/*
* 需求:过滤.bmp后缀的文件
*/
public class FileDemo2 {
public static void main(String[] args) {
File dir = new File("d:\\java\\day18");
String[] arr = dir.list(new FilenameFilter() { //匿名内部类,直接完成accept方法的复写
public boolean accept(File dir, String name) {
return name.endsWith(".bmp");
}
} );
System.out.println("Total:" + arr.length);
for (String name: arr) {
System.out.println(name);
}
}
}
递归是函数调用函数自身。
当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。
简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。
递归的注意事项:
1:一定要定义递归的条件。
2:递归的次数不要过多。容易出现StackOverflowError栈内存溢出错误。
其实递归就是在栈中不断地加载同一个函数。
应用:当某一功能需要重复使用时使用。
代码示例:
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/*
* 需求:获得某一目录下(含子目录)所有.java的文件,生成一个列表,导出成文本文件。
*/
public class JavaFileList {
public static void main(String[] args) {
File dir = new File("d:\\java");
ArrayList list = new ArrayList();
makeList(dir, list);
File file = new File(dir, "filelist.txt");
toFile(file.toString(), list);
}
/* 制作目录列表*/
public static void makeList(File dir, List list) {
File[] files = dir.listFiles(); // listFiles()返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
for (File file: files) {
if (file.isDirectory()) {
makeList(file, list); // 如果查找到的文件是目录,就递归,查找其目录下的文件
} else {
if (file.getName().endsWith(".java")) { // 如果不是目录,就添加该文件路径到list集合
list.add(file);
}
}
}
}
/* 生成文件 */
public static void toFile(String listFile, List list) {
BufferedWriter bufw = null;
try {
bufw = new BufferedWriter(new FileWriter(listFile));
} catch (IOException e) {
throw new RuntimeException("创建文件失败");
}
try {
for (File file: list) {
String path = file.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
} catch (IOException e) {
throw new RuntimeException("写入文件失败");
} finally {
if (bufw != null) {
try {
bufw.close();
} catch (IOException e) {
throw new RuntimeException("关闭输出流失败");
}
}
}
}
}
练习二:
import java.io.File;
/*
* 需求:删除一个带内容的目录
*/
public class RemoveDir {
public static void main(String[] args) {
File dir = new File("d:\\java");
removeDir(dir);
}
public static void removeDir(File dir) {
File[] files = dir.listFiles();
for (int x = 0; x < files.length; x++) { // 每个角标代表一个文件路径,因为要移除对应数组的角标,所以用普通for循环
if (files[x].isDirectory()) { // 如果判断结果是目录,就递归,进入到其目录下操作
removeDir(files[x]);
} else {
System.out.println(files[x].toString() + " :File: " + files[x].delete()); // 先删除文件
}
}
System.out.println(dir + " :Dir: " + dir.delete()); // 再删除目录
}
}
打印流提供了打印方法,可以将各种数据类型的数据照原样打印。
方法:
PrintStream():字节打印流。
构造函数可以接收的参数类型
File对象:File
字符串路径:String
字节输出流:OutputStream
PrintWriter():字符打印流。
构造函数可以接收的参数类型
File对象:File
字符串路径:String
字节输出流:OutputStream
字符输出流:Writer
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
/*
* 需求:在控制台中读入键盘录入字符,把字符转换成大写,输出到文件中
*/
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = null;
try {
out = new PrintWriter(new FileWriter("D:\\2.txt"), true);
for (String line = null; (line = bufr.readLine()) != null; ) {
if ("over".equals(line)) {
break;
}
out.println(line.toUpperCase()); //把所有字符转换成大写
}
} catch (IOException e) {
throw new RuntimeException("操作文件失败");
} finally {
out.close();
try {
bufr.close();
} catch (IOException e) {
throw new RuntimeException("关闭读取流失败");
}
}
}
}
序列流(SequenceInputStream)可以对多个流进行合并。
示例:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
* 需求:分割文件
*/
public class SplitFile {
public static void main(String[] args) {
FileInputStream fis;
try {
fis = new FileInputStream("d:\\1.mp3");
} catch (FileNotFoundException e1) {
throw new RuntimeException("找不到文件");
}
FileOutputStream fos = null;
byte[] buf = new byte[1024 * 1024]; // 每个部分1MB
try {
for (int len = 0, count = 1; (len = fis.read(buf)) != -1; ) {
fos = new FileOutputStream("d:\\" + (count ++) + ".part");
fos.write(buf, 0, len);
}
} catch (IOException e) {
throw new RuntimeException("操作文件失败");
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException("关闭输出流失败");
}
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException("关闭输入流失败");
}
}
}
}
}
mport java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
/*
* 需求:合并文件
*/
public class MergeFile {
public static void main(String[] args) throws IOException {
ArrayList al = new ArrayList(); // 定义一个集合,用字节流读取文件的部分,放入集合
for (int x = 1; x <= 3; x++) {
al.add(new FileInputStream("d:\\" + x + ".part"));
}
final Iterator it = al.iterator();
Enumeration en = new Enumeration() { // 用枚举遍历取出
public boolean hasMoreElements() {
return it.hasNext();
}
public FileInputStream nextElement() {
return it.next();
}
};
SequenceInputStream sis = new SequenceInputStream(en); // 导入序列流
FileOutputStream fos = new FileOutputStream("d:\\1.mp3"); // 合并成一个文件
byte[] buf = new byte[1024];
for (int len = 0; (len = sis.read(buf)) != -1; ) {
fos.write(buf, 0, len);
}
fos.close();
sis.close();
}
}
随机访问文件类(RandomAccessFile)具备读写的方法,通过skipBytes(int x),seek(int x)实现随机访问。
该类不算是io体系中的子类,而是直接继承自Object。但是它是IO包中的成员,因为它具备读和写的功能。其内部封装了字节输入流和输出流,通过指针对数组的元素进行操作。可以通过getFilePointer获取指针的位置,同时可以通过seek改变指针的位置。
通过构造函数可以看出,该类只能操作文件,而且操作的文件还有模式:r(只读),rw(读写)。
只读和读写的区别:
只读模式不会创建文件,而是去读取一个已存在的文件。如果该文件不存在,则会出现异常。
读写模式操作的文件如果不存在,会自动创建。如果存在,不会覆盖已存在的文件。
代码示例:
import java.io.IOException;
import java.io.RandomAccessFile;
/*
* 需求:用随机访问文件把2个人的姓名和年龄信息写入一个文件,并读取第二个人的信息。
*/
public class RandomAccessFileDemo {
public static void main(String[] args) {
readFile();
writeFile();
}
/* 读取文件 */
public static void readFile() {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("D:\\ran.txt", "r");
// 跳过指定的字节数
raf.skipBytes(8);
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
// 从此文件读取一个有符号的 32 位整数
int age = raf.readInt();
System.out.println("name = " + name);
System.out.println("age = " + age);
} catch (IOException e) {
throw new RuntimeException("操作文件失败");
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
throw new RuntimeException("关闭流失败");
}
}
}
}
/* 写入文件 */
public static void writeFile() {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("D:\\ran.txt", "rw");
raf.write("Lisi".getBytes());
raf.writeInt(97);
raf.write("Wang".getBytes());
raf.writeInt(99);
} catch (IOException e) {
throw new RuntimeException("写入文件失败");
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
throw new RuntimeException("关闭流失败");
}
}
}
}
}
管道流按字节流来分,分为管道输出流(PipedOutputStream)和管道输入流(PipedInputStream),利用 java.io.PipedOutputStream和java.io.PipedInputStream可以实现线程之间的二进制信息传输。如果要进行管道输出,则必须把输出流连在输入流上。
代码示例:
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
class Read implements Runnable {
private PipedInputStream in;
Read(PipedInputStream in) {
this.in = in;
}
public void run() {
try {
byte[] buf = new byte[1024];
System.out.println("读取前……没有数据阻塞");
int len = in.read(buf);
System.out.println("读到数据……阻塞结束");
String s = new String(buf, 0, len);
System.out.println(s);
} catch (IOException e) {
throw new RuntimeException("管道输入流失败");
} finally {
try {
in.close();
} catch (IOException e) {
throw new RuntimeException("关闭管道输入流失败");
}
}
}
}
class Write implements Runnable {
private PipedOutputStream out;
Write(PipedOutputStream out) {
this.out = out;
}
public void run() {
try {
System.out.println("开始写入数据,等待6秒");
Thread.sleep(6000);
out.write("管道输出".getBytes());
} catch (Exception e) {
throw new RuntimeException("管道输出流失败");
} finally {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException("关闭管道输出流失败");
}
}
}
}
public class PipedStreamDemo {
public static void main(String[] args) throws IOException {
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);
Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start();
new Thread(w).start();
}
}
ObjectInputStream与ObjectOutputStream:操作对象。被操作的对象需要实现Serializable(标记接口)。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/* 序列化处理对象 */
class Person implements Serializable {
public static final long serialVersionUID = 42L;
private String name;
private int age;
private String country;
Person(String name, int age, String country) {
this.name = name;
this.age = age;
this.country = country;
}
public String toString() {
return name + ":" + age + ":" + country;
}
}
public class ObjectStreamDemo {
public static void readObj() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\obj.txt"));
Person p = (Person) ois.readObject();
System.out.println(p);
ois.close();
}
public static void writeObj() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\obj.txt"));
oos.writeObject(new Person("Lisi", 26, "cn"));
oos.close();
}
public static void main(String[] args) throws Exception {
writeObj();
readObj();
}
}
DataInputStream与DataOutputStream用于操作基本数据类型。
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataStreamDemo {
/* 使用UTF-8编码读字节流 */
public static void readUTFDemo () throws IOException {
DataInputStream dis = new DataInputStream(new DataInputStream(new FileInputStream("d:\\utf.txt")));
String s = dis.readUTF();
System.out.println(s);
dis.close();
}
/* 使用UTF-8编码写字节流 */
public static void writeUTFDemo() throws IOException {
DataOutputStream dos = new DataOutputStream(new DataOutputStream(new FileOutputStream("d:\\utf.txt")));
dos.writeUTF("你好");
dos.close();
}
/* 使用基本数据类型读字节流 */
public static void readData() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("d:\\data.txt"));
int num = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();
System.out.println("num = " + num);
System.out.println("b = " + b);
System.out.println("d = " + d);
dis.close();
}
/* 使用基本数据类型写字节流 */
public static void writeData() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:\\data.txt"));
dos.writeInt(234);
dos.writeBoolean(true);
dos.writeDouble(9887.543);
dos.close();
}
public static void main(String[] args) throws IOException {
readUTFDemo();
writeUTFDemo();
readData();
writeData();
}
}
ByteArrayInputStream与ByteArrayOutputStream用于操作字节数组的流对象。
ByteArrayInputStream:在构造的时候,需要接收数据源,而且数据源是一个字节数组。
ByteArrayOutputStream:在构造的时候,不需要定义数据目的,因为该对象中已经内部封装了可变长度的字节数组,这就是数据的目的地。
因为这两个流对象都操作的是数组,并没有使用系统资源。所以,不用进行close关闭。
代码示例:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class ByteArrayStream {
public static void main(String[] args) {
ByteArrayInputStream bis = new ByteArrayInputStream("ABCDE".getBytes());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (int by = 0; (by = bis.read()) != -1; ) {
bos.write(by);
}
System.out.println(bos.size());
System.out.println(bos.toString());
}
}
字符流的出现是为了方便操作字符,更重要的是加入了编码转换。
字符编码的使用:通过子类转换流来完成。
InputStreamReader
OutputStreamWriter
在两个对象进行构造的时候可以加入字符集。
计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。
ASCLL:美国标准信息交换码。用一个字节的7位表示。
ISO8859-1:拉丁码表,欧洲码表。用一个字节的8位表示。
GB2312:中国的中文编码表。
GBK:中国的中文编码表升级。融合了更多的中文文字符号。
Unicode:国际标准码。融合了多种文件。
UTF-8:最多用三个字节来表示一个字符。
转换流的编解码应用:
可以将字符以指定编码格式存储,指定编码表的动作由构造函数完成。
可以对文本数据指定编码格式来解读。
注意:
如果编码失败,解码就没意义了。
如果编码成功,使用某解码表解出来的是乱码,则需对乱码用该解码表再次编码,然后再通过正确的编码表解码。适用于IOS8859-1。
如果用的是GBK编码,UTF-8解码,此时通过再次编码后解码的方式,就不能成功了。因为UTF-8解码的时候,会将对应的字节数改变。
字符串转成字节数组:
byte[] b = str.getBytes(charsetName);
字节数组转成字符串:
String str = new String(byte[],charsetName);
代码示例1:
import java.util.Arrays;
/*
* 需求:编码后解码
*/
public class EncodeDemo {
public static void main(String[] args) throws Exception {
String s = "你好";
// 字符串转成字节数组,用gbk编码
byte[] b1 = s.getBytes("gbk");
System.out.println(Arrays.toString(b1));
// 字节数组转成字符串,用iso8859-1编码
String s1 = new String(b1, "iso8859-1");
System.out.println("s1 = " + s1);
// 字符串转成字节数组,用iso8859-1解码
byte[] b2 = s1.getBytes("iso8859-1");
System.out.println(Arrays.toString(b2));
// 字节数组转成字符串,用gbk解码
String s2 = new String(b2, "gbk");
System.out.println("s2 = " + s2);
}
}
代码示例2:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class EncodeStream {
public static void main(String[] args) throws Exception {
writeText();
readText();
}
/* 未使用正确的编码进行解读将会读出乱码 */
public static void readText() throws Exception {
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\utf.txt"));
char[] buf = new char[10];
int len = isr.read(buf);
String str = new String(buf, 0, len);
System.out.println(str);
isr.close();
}
/* 对内容进行utf-8编码 */
public static void writeText() throws Exception {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\utf.txt"), "utf-8");
osw.write("你好");
osw.close();
}
}
——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-