黑马程序员—IO流
------- android培训、java培训、期待与您交流! -----------------
1.File类
File类是用来将文件或文件夹封装成对象的类,方便对文件与文件夹的属性信息进行操作。如果需要在程序中操作文件和目录,都是可以通过File类来完成。File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用IO流。
File类的常用操作方法:
import java.io.*;
public class FileMethod
{
public static void main(String[] args)
throws IOException
{
// 创建一个File对象
File file = new File("D;\\a.txt");
// 获取文件名
System.out.println(file.getName());
// 获取相对路径的父路径
System.out.println(file.getParent());
// 获取绝对路径
System.out.println(file.getAbsoluteFile());
// 获取上一级路径
System.out.println(file.getAbsoluteFile().getParent());
// 在当前路径下创建一个临时文件
File tmpFile = File.createTempFile("aaa", ".txt", file);
// 指定当JVM退出时删除该文件
tmpFile.deleteOnExit();
// 以系统当前时间作为新文件名来创建新文件
File newFile = new File(System.currentTimeMillis() + "");
System.out.println("newFile对象是否存在:" + newFile.exists());
// 以指定newFile对象来创建一个文件
newFile.createNewFile();
// 以newFile对象来创建一个目录,因为newFile已经存在,
// 所以下面方法返回false,即无法创建该目录
newFile.mkdir();
// 使用list()方法来列出当前路径下的所有文件和路径
String[] fileList = file.list();
System.out.println("====当前路径下所有文件和路径如下====");
for (String fileName : fileList)
{
System.out.println(fileName);
}
// listRoots()静态方法列出所有的磁盘根路径。
File[] roots = File.listRoots();
System.out.println("====系统所有根路径如下====");
for (File root : roots)
{
System.out.println(root);
}
}
}
//列出指定目录下的所有文件
public static void showDir(File dir)
{
System.out.println(dir);
//列出所有文件及目录
File[] files=dir.listFiles();
for(int i=0;i<files.length;i++)
{
if(files[i].isDirectory())
//递归调用
showDir(files[i]);
else
System.out.println(files[i]);
}
}
总结:
(1)File既可以表示一个文件,也可以表示一个目录;
(2)File类的对象是与平台无关的;
(3)File类针对文件或文件目录,只能进行新建、删除等属性操作。如果涉及访问文件内容,File是无能为力的,只能使用IO流下提供的相应的IO流来实现;
(4)常把File类的对象作为形参传递给相应的IO流的构造函数中。
2.IO流
2.1 流的分类
(1)按照流的流向的不同分为输入流和输出流:
输入流:只能从中读取数据,而不能向其写入数据。主要由InputStream和Reader作为基类。
输出流:只能向其写入数据,而不能从中读取数据。主要由OutputStream和Writer作为基类。
(2)按照流中数据单位的不同分为字节流和字符流:
字节流:数据单位为字节。主要由InputStream和OutputStream作为基类。
字符流:数据单位为字符。主要由Reader和Writer作为基类。
2.2 字节流和字符流的相关操作
InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所有它们的方法是所有输入流都可使用的方法。
OutputStream和Writer与之相似。
代码示例:
import java.io.*;
//字节流来实现文件的复制
public class FileOutputStreamTest
{
public static void main(String[] args)
{
// 创建字节输入流和字节输出流
FileInputStream fis=null;
FileOutputStream fos=null;
try
{
fis = new FileInputStream("Test.java");
fos = new FileOutputStream("newFile.txt");
byte[] bbuf = new byte[32];
int hasRead = 0;
// 循环从输入流中取出数据
while ((hasRead = fis.read(bbuf)) > 0 )
{
// 每读取一次,即写入文件输出流,读了多少,就写多少。
fos.write(bbuf , 0 , hasRead);
}
}
catch (IOException ioe)
{
throw new RuntimeException("读写失败!");
}
finally
{
if(fis!=null)
try {
fis.close();
} catch (IOException e) {
}
if(fos!=null)
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException("关闭输出流失败");
}
}
}
}
字符流:
import java.io.*;
public class FileReaderTest
{
public static void main(String[] args)
{
// 创建字符输入流
FileReader fr=null;
try
{
fr= new FileReader("FileReaderTest.java");
// 创建字符数组
char[] cbuf = new char[32];
// 用于保存实际读取的字符数
int hasRead = 0;
// 循环读取
while ((hasRead = fr.read(cbuf)) > 0 )
{
// 将字符数组转换成字符串
System.out.print(new String(cbuf , 0 , hasRead));
}
}
catch (IOException ex)
{
throw new RuntimeException("读取失败!");
}
finally
{
if(fr!=null)
try {
fr.close();
} catch (IOException e) {
throw new RuntimeException("关闭输入流失败!");
}
}
}
}
import java.io.*;
public class FileWriterTest
{
public static void main(String[] args)
{
FileWriter fw=null;
try
{
fw= new FileWriter("text.txt");
fw.write("helloworld!\r\n");
}
catch (IOException ioe)
{
throw new RuntimeException("写入失败!");
}
finally
{
if(fw!=null)
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException("关闭输出流失败!");
}
}
}
}
字符流的缓冲区:对应类--BufferedWriter和BufferedReader
缓冲区的出现是为了提高流的操作效率,所以在创建缓冲区之前,必须要有流对象。
BufferedWriter:字符流写入缓冲区,该缓冲区提供了一个跨平台的换行符:newLine()。
BufferedReader:字符流读取缓冲区,该缓冲区提供了一个读取一行的方法:readLine(),以便于对文本数据的获取,当返回null时表示读到文件末尾。
示例:通过缓冲区复制一个.java文件。
import java.io.*;
public class CopyTextByBuf {
public static void main(String[] args) {
//创建流对象
BufferedReader bufr=null;
BufferedWriter bufw=null;
try {
bufr=new BufferedReader(new FileReader("Person.java"));
bufw=new BufferedWriter(new FileWriter("copy.txt"));
String line=null;
//循环读取
while((line=bufr.readLine())!=null)
{
//将读取的数据写入流中
bufw.write(line);
//换行
bufw.newLine();
//刷新
bufw.flush();
}
}
catch (IOException e) {
throw new RuntimeException("读写失败!");
}
finally
{
if(bufr!=null)
try {
bufr.close();
} catch (IOException e) {
throw new RuntimeException("关闭读取流失败!");
}
if(bufw!=null)
try {
bufw.close();
} catch (IOException e) {
throw new RuntimeException("关闭写入流失败!");
}
}
}
}
2.3 转换流
IO体系中提供了两个转换流,用于实现将字节流转换为字符流,其中:
InputStreamReader将字节输入流转换为字符输入流;
OutputStreamWriter将字节输出流转换为字符输出流。
代码示例:
import java.io.*;
public class KeyinTest
{
public static void main(String[] args)
{
InputStreamReader reader=null;
BufferedReader br=null;
try
{
// 将Sytem.in对象转换成Reader对象
reader= new InputStreamReader(System.in);
//将普通Reader包装成BufferedReader
br= new BufferedReader(reader);
String buffer = null;
//采用循环方式来一行一行的读取
while ((buffer = br.readLine()) != null)
{
//如果读取的字符串为"exit",程序退出
if (buffer.equals("exit"))
{
System.exit(1);
}
//打印读取的内容
System.out.println("输入内容为:" + buffer);
}
}
catch (IOException ioe)
{
throw new RuntimeException("读写失败!");
}
finally
{
if(br!=null)
try {
br.close();
} catch (IOException e) {
throw new RuntimeException("关闭流失败!");
}
}
}
}
2.4 打印流:PrintWriter和PrintStream。
该流提供了打印方法,可以将各种数据类型的数据都原样打印。
(1)字节打印流:PrintStream
此类的构造函数可以接收的参数类型有:File、String、OutputStream。
(2)字符打印流:PrintWriter
此类的构造函数可以接收的参数类型有:File、String、OutputStream、Writer。
代码示例:
//接收键盘输入的字符串将其转换为大写在控制台上输出
import java.io.*;
public class PrintWriterTest {
public static void main(String[] args) {
//创建输入输出流对象
BufferedReader bufr=null;
PrintWriter pw=null;
try {
//接收键盘收入
bufr=new BufferedReader(new InputStreamReader(System.in));
pw=new PrintWriter(System.out,true);
String line=null;
while((line=bufr.readLine())!=null)
{
if(line.equals("over"))
break;
pw.println(line.toUpperCase());
}
}
catch (IOException e) {
throw new RuntimeException("读写失败!");
}
finally
{
if(bufr!=null)
try {
bufr.close();
} catch (IOException e) {
throw new RuntimeException("关闭流失败!");
}
if(pw!=null)
pw.close();
}
}
}
对象的序列化是指将内存中的Java对象转换成与平台无关的二进制流,从而把这种二进制流持久地保存在硬盘上。如果需要让某个对象实现序列化,则必须让其实现Serializable接口。
使用对象流来实现序列化:
//人类
import java.io.Serializable;
class Person implements Serializable
{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
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;
}
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public static void writeObj()
{
//创建写入对象流
ObjectOutputStream oos=null;
try
{
oos=new ObjectOutputStream(new FileOutputStream("person.obj"));
//向文件中写入对象信息
oos.writeObject(new Person("张三",20));
}
catch (IOException e) {
throw new RuntimeException("创建文件失败!");
}
finally
{
if (oos!=null)
try {
oos.close();
} catch (IOException e) {
throw new RuntimeException("关闭流发生异常!");
}
}
}
public static void readObj()
{
//创建读取对象流
ObjectInputStream ois=null;
try
{
ois=new ObjectInputStream(new FileInputStream("person.obj"));
//读取文件中的对象信息
Person p=(Person)ois.readObject();
}
catch (Exception e) {
throw new RuntimeException("创建文件失败!");
}
finally
{
if (ois!=null)
try {
ois.close();
} catch (IOException e) {
throw new RuntimeException("关闭流发生异常!");
}
}
}
注意:
(1)静态成员是不能被序列化的;
(2)如果对非静态的成员不想被序列化的话,可以加上transient修饰。
2.5 RandomAccessFile:随机访问文件,其自身具备读写的方法。
该类不属于IO体系的子类,其直接继承Object,但它是IO包中的成员,因为它具备读和写的功能。其内部封装了一个数组,而且可以通过指针对数组的元素进行操作,并且可以通过getFilePointer()来获取指针的位置,同时可以通过seek改变指针的位置。其完成读写的原理就是内部封装了字节输入流和输出流,通过构造函数可以看出此类只能操作硬盘上的文件,而且操作还有规定模式:如只读(r)、读写(rw)等等。如果模式为只读,操作文件时不会创建文件而是会读取一个已经存在的文件,若文件不存在则会出现异常;如果模式为读写,操作文件时,若文件不存在会自动创建文件,若文件存在不会覆盖。
public class RandomAccessFileTest
{
public static void main(String[] args)
{
RandomAccessFile raf=null;
try
{
raf = new RandomAccessFile("FileTest.java" , "r");
// 获取RandomAccessFile对象文件指针的位置,初始位置是0
System.out.println("File的文件指针的初始位置:" + raf.getFilePointer());
// 移动raf的文件记录指针的位置
raf.seek(300);
byte[] bbuf = new byte[1024];
// 用于保存实际读取的字节数
int hasRead = 0;
// 循环读取
while ((hasRead = raf.read(bbuf)) > 0 )
{
// 将读取的字节数组转换成字符串输入!
System.out.print(new String(bbuf , 0 , hasRead ));
}
}
catch (IOException ex)
{
throw new RuntimeException("操作失败!");
}
finally
{
if(raf!=null)
try {
raf.close();
} catch (IOException e) {
throw new RuntimeException("操作失败!");
}
}
}
}
2.6 数据流:DataInputStream和DataOutputStream
此流是用来操作基本数据类型的。
public static void readData()
{
//创建数据读取流对象
DataInputStream dis=null;
DataOutputStream dos=null;
try
{
dis=new DataInputStream(new FileInputStream("data.txt"));
dos=new DataOutputStream(new FileOutputStream("data.txt"));
//写入数据
dos.writeBoolean(true);
//读取数据
boolean b=dis.readBoolean();
dos.writeDouble(2.2d);
double d=dis.readDouble();
dos.writeFloat(1.1f);
float f=dis.readFloat();
dos.writeInt(10);
int i=dis.readInt();
//使用writeUTF写入的数据必须是使用与之对应的读取方法才能读出来
dos.writeUTF("大家好!");
String s=dis.readUTF();
}
catch (IOException e) {
throw new RuntimeException("操作失败!");
}
finally
{
if(dos!=null)
try {
dos.close();
} catch (IOException e) {
throw new RuntimeException("操作失败!");
}
if(dis!=null)
try {
dis.close();
} catch (IOException e) {
throw new RuntimeException("操作失败!");
}
}
}
2.7 数组流:用流的思想来操作数组
字节数组流:ByteArrayInputStream和ByteArrayOutputStram
字符数组流:CharArrayInputStream和CharArrayOutputStram
字符串数组流:StringArrayInputStream和StringArrayOutputStram
以字节数组流为例:
ByteArrayInputStream:在构造的时候需要接收数据源,而且数据源是一个字节数组。
ByteArrayOutputStram:在构造的时候不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组,这就是目的地。
注意:因为这两个流对象都是操作的数组并没有使用系统资源,所以,不用进行资源的关闭,也没有异常的发生。
public class ByteArrayTest {
public static void main(String[] args) {
//数据源
ByteArrayInputStream bis=new ByteArrayInputStream("abcdefg".getBytes());
//数据目的
ByteArrayOutputStream bos=new ByteArrayOutputStream();
int by=0;
while((by=bis.read())!=-1)
{
bos.write(by);
}
System.out.println(bos.toString());
}
}
3. NIO
从JDK1.4开始,Java提供了一系列改进的输入、输出处理的新功能,这些功能被统称为新IO(NIO)。
新IO中的一些常用的类
(1)Buffer:表示缓冲,可以理解为一个数组容器。
Buffer中有三个重要的概念:容量(capacity),界限(limit)和位置(position)
容量:表示该缓冲区的最大数据容量;
界限:表示第一个不应该被读出或者写入的缓冲区位置索引,也就是说,位于界限处的数据既不可以被读,也不可以被写。
位置:用于指明下一个可以被读出的或者写入的缓冲区位置索引。
代码示例:
import java.nio.*;
public class BufferTest
{
public static void main(String[] args)
{
// 创建Buffer
CharBuffer buff = CharBuffer.allocate(8);
System.out.println("capacity: " + buff.capacity());
System.out.println("limit: " + buff.limit());
System.out.println("position: " + buff.position());
// 放入元素
buff.put('a');
buff.put('b');
buff.put('c');
System.out.println("加入三个元素后,position = "+ buff.position());
// 调用flip()方法,使limit回到原位置处
buff.flip();
System.out.println("执行flip()后,limit = " + buff.limit());
System.out.println("position = " + buff.position());
// 取出第一个元素
System.out.println("第一个元素(position=0):" + buff.get()); //
System.out.println("取出一个元素后,position = "
+ buff.position());
// 调用clear方法
buff.clear();
System.out.println("执行clear()后,limit = " + buff.limit());
System.out.println("执行clear()后,position = "+ buff.position());
System.out.println("执行clear()后,缓冲区内容并没有被清除:"+ "第三个元素为:" + buff.get(2)); // ⑥
System.out.println("执行绝对读取后,position = "+ buff.position());
}
}
(2)Channel:表示通道。Channel是对传统的IO流系统的模拟,在新IO系统中所有的数据都需要通过通道传输;Channel与传统的IO的最大区别在于它提供了一个map方法,通过map可以直接将一块数据映射到内存中,如果说传统IO是面向流的处理,那么新IO就是面向块的处理。
Channel中最常用的3个方法是map()、read()、write()。
map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer;而read()和write()方法用于从Buffer中读取数据和向Buffer中写入数据。
代码示例:
将FileChannel中的全部数据映射成ByteBuffer:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
public class FileChannelTest
{
public static void main(String[] args)
{
File f = new File("FileChannelTest.java");
try(
// 创建FileInputStream,以该文件输入流创建FileChannel
FileChannel inChannel = new FileInputStream(f).getChannel();
// 以文件输出流创建FileBuffer,用以控制输出
FileChannel outChannel = new FileOutputStream("a.txt").getChannel())
{
// 将FileChannel里的全部数据映射成ByteBuffer
MappedByteBuffer buffer = inChannel.map(FileChannel
.MapMode.READ_ONLY , 0 , f.length());
// 使用GBK的字符集来创建解码器
Charset charset = Charset.forName("GBK");
// 直接将buffer里的数据全部输出
outChannel.write(buffer);
// 再次调用buffer的clear()方法,复原limit、position的位置
buffer.clear();
// 创建解码器(CharsetDecoder)对象
CharsetDecoder decoder = charset.newDecoder();
// 使用解码器将ByteBuffer转换成CharBuffer
CharBuffer charBuffer = decoder.decode(buffer);
// CharBuffer的toString方法可以获取对应的字符串
System.out.println(charBuffer);
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
4.字符编码
编码(Encode):把字符序列转换成二进制序列。
解码(Decode):将二进制序列转换为能看懂的字符序列。
常用编码:
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表或欧洲码表。用一个字节的8位表示。
GB2312:中国的中文编码表。
GBK:中国的中文编码表升级,融合了更多的中文文字符号。
Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode。
UTF-8:最多用三个字节来表示一个字符。
总结:
IO中的四个抽象基类:
InputStream OutputStream
Reader Writer
文件流:
FileInputStream FileOutputStream
FileReader FileWriter
缓冲处理流
BufferedInputStream BufferedOutputStream
BufferedReader BufferedWriter
转换流
InputStreamReader
OutputStreamWriter
标准输入输出流
System.in
System.out
注意:
(1)从硬盘上读入一个文件,要求此文件一定得存在。若不存在,则会发生FileNotFoundException的异常。
(2)从程序中输出一个文件到硬盘,此文件可以不存在。若不存在,就创建一个;若存在,则会将存在的文件覆盖。
(3)真正开发时,应当使用缓冲流来提供效率。
(4)注意要关闭相应的流,以释放资源。
------- android培训、java培训、期待与您交流! ----------