一、简述
Java提供了完整的IO操作,IO分为输入流和输出流,Java又分为了字节和字符两大类。字符专门用来处理字符型数据,非常方便;字节也可处理文本数据,但是更多地是用来处理非文本的数据。
二、基于"字节"的IO
1、InputStream和OutputStream
基于字节的IO中的输入和输出分别为InputStream类和OutputStream类,这两个类都是抽象类。
2、FileInputStream和FileOutputStream
FileInputStream用于从文件中读取信息,FileOutputStream用于往文件中写入信息。
可以这样从文件中读取数据:
FileInputStream fis = new FileInputStream("buf.txt");
int length = 0;
byte[] buf = new byte[1024];
while((length = fis.read(buf)) != -1) {
String str = new String(buf, 0, length);
System.out.println(str);
}
fis.close()
用FileOutputStream读取数据:
FileOutputStream fos = new FileOutputStream("buf.txt");
byte[] out = "12342134436435\\n".getBytes();
fos.write(out);
out = "hsdkafhlsadfhsadf".getBytes();
fos.write(out);
fos.close();
3、BufferedInptuStream和BufferedOutputStream
BufferedInputStream和BufferedOutputStream为输入输出加入了缓冲区,这样读取起来更加高效。
自己也可以实现同样功能的类,如可以这样设计:
import java.io.*;
public class MyBufferedInputStream {
private InputStream in;
private byte[] buf = new byte[1024 * 4];
private int pos = 0, count = 0;
MyBufferedInputStream(InputStream in)
{
this.in = in;
}
//一次读一个字节,从缓冲区(字节数组)获取
public int myRead() throws IOException
{
//通过in对象读取硬盘上数据,并存储buf中
if(count == 0)
{
count = in.read(buf);
if(count < 0)
return -1;
pos = 0;
byte b = buf[pos];
count--;
pos++;
return b&0xff; //b may be read 11111111B in the memory,decimal is -1,that will be make an error.
}
else if (count > 0)
{
byte b = buf[pos];
count--;
pos++;
return b&255;
}
return -1;
}
public void myClose() throws IOException
{
in.close();
}
}
import java.io.*;
public class BufferedStreamDemo {
public static void main(String[] args) throws IOException{
BufferedOutputStreamDemo();
BufferedInputStreamDemo();
}
public static void BufferedInputStreamDemo() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("buf.txt"));
int buf;
while((buf = bis.read()) != -1) {
System.out.print((char)buf);
}
bis.close();
}
public static void BufferedOutputStreamDemo() throws IOException{
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("buf.txt"));
byte[] buf = "buffered output stream content.\\n".getBytes();
bos.write(buf);
buf = "second row.\\n".getBytes();
bos.write(buf);
bos.flush(); //刷新缓冲区
bos.close();
}
}
4、DataInputStream和DataOutputStream
DataInputStream和DataOutputStream是专门用于操作基本数据类型的流对象。可以以指定类型输入输出数据。
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
writeData();
readData();
writeUTFDemo();
readUTFDemo();
}
public static void readData() throws IOException
{
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
int num = dis.readInt();
double d = dis.readDouble();
boolean bl = dis.readBoolean();
System.out.println(num + " " + d + " " + bl);
dis.close();
}
public static void readUTFDemo() throws IOException
{
DataInputStream dis = new DataInputStream(new FileInputStream("utfdata.txt"));
String s = dis.readUTF();
System.out.println(s);
dis.close();
}
public static void writeData() throws IOException
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeInt(243);
dos.writeDouble(2.34234);
dos.writeBoolean(true);
dos.close();
}
public static void writeUTFDemo() throws IOException
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfdata.txt"));
dos.writeUTF("你好");
dos.close();
}
}
输出为:
243 2.34234 true
你好
5、PipedInputStream和PipedOutputStream
管道流也用于传输数据,输入管道流与输出管道流连接,输入管道流提供要写入管道输出流的所有数据字节。
不建议使用单线程,因为会发生死锁问题。
class Read implements Runnable
{
private PipedInputStream in;
Read(PipedInputStream in)
{
this.in = in;
}
public void run()
{
try {
byte[] buf = new byte[1024];
int len = in.read(buf);
String s = new String(buf, 0, len);
System.out.println(s);
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 {
out.write("piped arrived".getBytes());
}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();
}
}
6、ByteArrayInputStream和ByteArrayOutputStream
这两个流对象专门用来操作字节。
ByteArrayInputStream在构造的时候,需要接收数据源,而且数据是一个字节数据。
ByteArrayOutputStream在构造的时候,不用定义数据目的,因为该对象内部中已经封装了可变长度的字节数组。
因为这两个流对象操作的都是数组,并未使用系统资源,所以不用进行关闭。
源设备可以有:
- 键盘:System.in
- 硬盘:FileStream
- 内存:ArrayStream
目的设备可以有:
- 控制台:System.out
- 硬盘:FileStream
- 内存:ArrayStream
//数据源
ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFD".getBytes());
//数据目的
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int by = 0;
while((by = bis.read()) != -1)
{
bos.write(by);
}
System.out.println(bos.size());
System.out.println(bos.toString());
输出为:
7
ABCDEFD
7、SequenceInputStream
SequenceInputStream流对象可以将两个或多个InputStream转换为单一InputStream。
Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream("1.txt"));
v.add(new FileInputStream("2.txt"));
v.add(new FileInputStream("3.txt"));
Enumeration<FileInputStream> en = v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = new FileOutputStream("123.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = sis.read(buf)) != -1)
{
fos.write(buf, 0, len);
}
fos.close();
sis.close();
8、LineNumberInputStream
此类可以跟踪流中的行号,其实就是传入一个计数变量,每读取一行打印一次罢了,这个流已经过时了,可以看LineNumberReader类。
9、StringBufferInputStream
此类也过时了,可以看StringReader类。
10、PrintStream
PrintStream是打印流,此类提供了打印方法,可以将各种数据类型都原样打印。
构造函数可以接收的参数类型:
- file对象。File
- 字符串路径。String
- 字节输出流。OutputStream
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("buf.txt"));
PrintStream ps = new PrintStream(System.out, true);
int buf = 0;
while((buf = bis.read()) != -1) {
ps.print((char)buf);
}
bis.close();
三、基于"字符"的IO
1、Reader和Writer
Reader和Writer分别对应InputStream和OutputStream的字符形式,用来表示字符IO的输入和输出流,这两个类也是抽象类。
2、InputStreamReader和OutputStreamWriter
InputStreamReader是读取转换流,OutputStream是输出转换流,这两个流搭建起了字节IO和字符IO的桥梁。
public class TranslateStream {
public static void main(String[] args) throws IOException{
InputStream in = System.in;
InputStreamReader isr = new InputStreamReader(in); //byte stream to char stream
BufferedReader bufr = new BufferedReader(isr); //use buffer to improve speed
String line = null;
while((line = bufr.readLine()) != null)
{
if("over".equals(line)) //exit condition
break;
System.out.println(line.toUpperCase());
}
bufr.close();
}
}
3、FileReader和FileWriter
此二类分别对应FileInputStream和FileWriter类。
//创建一个文件读取流对象,和指定名称的文件相关联
//要保证该文件是已经存在的,若不存在,抛异常FileNotFoundException
FileReader fr = new FileReader("demo.txt");
int ch;
while((ch = fr.read()) != -1) {
System.out.print((char)ch);
}
char[] text = new char[20];
int n = fr.read(text);
System.out.println("n:"+ n + new String(text));
fr.close();
//有同名文件将被覆盖,没有则创建
//即明确数据要存放的目的地
FileWriter fw = null;
try {
fw = new FileWriter("demo.txt");
fw.write("abcde"); //向流中写数据
//fw.flush(); //刷新缓冲区
fw.write("jhahaha"); //向流中写数据
}catch(IOException e) {
e.printStackTrace();
}finally {
try {
if(fw != null) {
//关闭流资源,关闭资源之前会刷新一次缓冲区
fw.close();
}
}catch(IOException e) {
e.printStackTrace();
}
}
4、BufferedReader和BufferedWriter
见名便知此二类是字符型的缓冲区流,因为是字符流,因此可以直接用String接收。
FileReader fr = new FileReader("buf.txt");
BufferedReader bufr = new BufferedReader(fr);
String str;
while((str = bufr.readLine()) != null) {
System.out.println(str);
}
bufr.close();
//创建一个字符写入流对象
FileWriter fw = new FileWriter("buf.txt", true);
//为了提高字符写入操作,加入了缓冲技术
//只要将需要被提高效率的流对象作为参数传递给缓冲区
BufferedWriter bufw = new BufferedWriter(fw);
for(int i = 0; i < 5; i++) {
bufw.write("abdedsd" + i);
bufw.newLine(); //换行符,可跨平台
bufw.flush(); //刷新缓冲区
}
//只要用到缓冲区,就要记得刷新
//bufw.flush();
//其实关闭缓冲区,就是在关闭缓冲区中的流对象,故不用写fw.close了
bufw.close();
5、PipedReader和PipedWriter
对应字节型的管道流对象。
6、CharArrayReader和CharArrayWriter
用于操作字符数组。
7、LineNumberReader
FileReader fr = new FileReader("buf_copy.txt");
LineNumberReader lnr = new LineNumberReader(fr);
String line = null;
lnr.setLineNumber(5); //设置行号
while((line = lnr.readLine()) != null)
{
System.out.println(lnr.getLineNumber() + ":" + line);
}
lnr.close();
8、PrintWriter
字符打印流构造函数可以接收的参数类型有:
- file对象。File
- 字符串路径。String
- 字节输出流。OutputStream
- 字符输出流。Writer
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//PrintWriter out = new PrintWriter(System.out,true);
PrintWriter out = new PrintWriter(new FileWriter("printwriter.txt"), true);
String line = null;
while((line = bufr.readLine()) != null)
{
if("over".equals(line))
break;
out.println(line.toUpperCase());
//out.flush();
}
out.close();
bufr.close();
9、StringBuffer和StringBuilder
StringBuffer类是线程安全的可变字符序列,是一个类似String的字符串缓冲区,但不能修改。
StringBuffer主要操作append()和insert方法,可以重载来接收任意类型的数据。每个方法才能有效地将给定的数据转换成字符串,然后将字符串的字符追加或插入到字符串缓冲区中。
StringBuilder类是StringReader的简单替换,若是字符串被单个线程使用,则建议使用StringBuilder。
四、基于"磁盘"的IO
1、File
IO流可以对流进行操作却不可直接操作文件,File类在IO包中唯一代表磁盘文件本身,该类定义了一些和平台无关的方法来操作文件。
File file = new File("F://Project//java//Fileiostream//test.txt");
if(file.exists()) {
String name = file.getName();
String parent = file.getParent();
long length = file.length();
boolean bool = file.canWrite();
System.out.println("file name:" + name);
System.out.println("file parent:" + parent);
System.out.println("file size:" + length);
System.out.println("是否为可写文件?" + bool);
}
File dir = new File("F://Project//java//");
if(dir.isDirectory()) {
File[] files = dir.listFiles();
for(int i = 0; i < files.length; i++) {
File f = files[i];
System.out.println("第" + (i + 1) + "个文件的名称是:" + f.getAbsolutePath());
}
}
2、RandomAccessFile
该类不算是IO体系的子类,而是直接继承自Object。但是它可操作IO包中成员,因其具备读和写功能。
内部封装了一个数组,而且通过指针对元素进行操作,可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。
其实完成读写的原理就是内部封装了字节输入流和输出流。
通过构造函数可看出其只能操作文件且操作文件还有模式:r,rw...
如果模式为r,不会创建文件,会去读一个已存在的文件,如果该文件不存在,则会报异常。
如果模式为rw,操作的文件不存在,会自动创建,如果存在不会覆盖。
而且该对象的构造函数要操作的文件不存在,会自动创建,若存在不会覆盖。
因为该类支持随机读写访问文件,所以可以通过seek来实现多线程文件下载。
public static void readFile() throws IOException
{
RandomAccessFile raf = new RandomAccessFile("ran.txt", "r");
//调整对象中指针
//raf.seek(7);
//跳过指定的字节数
raf.skipBytes(7);
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println("name:" + name);
System.out.println("age:" + age);
raf.close();
}
public static void writeFile() throws IOException
{
RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");
raf.write("neo".getBytes());
raf.writeInt(97); //write只写低8位,会丢失,故用其提供的带Int版本的.
raf.write("mike".getBytes());
raf.writeInt(108); //write只写低8位,会丢失,故用其提供的带Int版本的.
raf.close();
}
public static void writeFile_2() throws IOException
{
RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");
raf.seek(8*0);
raf.write("周期".getBytes());
raf.writeInt(37);
raf.close();
}
五、练习
1、复制图片
思路:
- 用字节读取流对象和图片关联
- 用字节写入流对象创建一个图片文件。用于存储获取到的图片数据。
- 通过循环读写,完成数据的存储
- 关闭资源
实现:
public class CopyPic {
public static void main(String[] args) {
FileOutputStream fos = null;
FileInputStream fis = null;
try
{
fos = new FileOutputStream("cpp_copy.png");
fis = new FileInputStream("C++.png");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1)
{
fos.write(buf, 0, len);
}
System.out.println("复制成功!");
}
catch(IOException e)
{
throw new RuntimeException("复制文件失败.");
}
finally
{
try
{
if (fos != null)
{
fos.close();
}
}catch(IOException e)
{
e.printStackTrace();
}
try
{
if (fis != null)
{
fis.close();
}
}catch(IOException e)
{
e.printStackTrace();
}
}
}
}
2、成绩录入
有五个学生,每个学生有3门课的成绩
从键盘录入以上数据
输入的格式:如:zhangsan,30,40,60计算出总成绩
并把学生的信息和计算出的部分数高低存放在磁盘文件"stud.txt"中
- 描述一个学生对象
- 定义一个可操作学生对象的工具类
思路:
- 通过获取键盘录入一行数据,并将该行中的信息取出封装成学生对象.
- 因为学生很多,所以用集合存储.因为以要排序,因而可以使用TreeSet.
- 将集合的信息写入到一个文件中.
实现:
class Student implements Comparable<Student>
{
private String name;
private int math, chinese, english;
private int sum;
Student(String name, int ma, int ch, int en)
{
this.name = name;
this.math = ma;
this.chinese = ch;
this.english = en;
this.sum = ma + ch + en;
}
public String getName()
{
return this.name;
}
public int getSum()
{
return sum;
}
public int hashCode()
{
return name.hashCode() + sum * 78;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Student))
throw new ClassCastException("类型不匹配");
Student s = (Student)obj;
return this.name.equals(s.name) && this.sum==s.sum;
}
public String toString()
{
return "student[" + name + " math:" + math + " chinese:" + chinese + " english:" + english + "]";
}
@Override
public int compareTo(Student s) {
int num = new Integer(this.sum).compareTo(new Integer(s.sum));
if(num == 0)
return this.name.compareTo(s.name);
return num;
}
}
class StudentInfoTool
{
public static Set<Student> getStudents() throws IOException
{
return getStudents(null);
}
public static Set<Student> getStudents(Comparator<Student> cmp) throws IOException
{
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
Set<Student> stus = null;
if(cmp == null)
stus = new TreeSet<Student>();
else
stus = new TreeSet<Student>(cmp);
while((line = bufr.readLine()) != null)
{
if("over".equals(line))
break;
String[] info = line.split(",");
Student stu = new Student(info[0], Integer.parseInt(info[1]),
Integer.parseInt(info[2]),
Integer.parseInt(info[3]));
stus.add(stu);
}
bufr.close();
return stus;
}
public static void write2File(Set<Student> stus) throws IOException
{
BufferedWriter bufw = new BufferedWriter(new FileWriter("stuinfo.txt"));
for(Student stu : stus)
{
bufw.write(stu.toString() + "\\t");
bufw.write(stu.getSum() + "");
bufw.newLine();
bufw.flush();
}
bufw.close();
}
}
public class Practice {
public static void main(String[] args) throws IOException {
Comparator<Student> cmp = Collections.reverseOrder();
Set<Student> stus = StudentInfoTool.getStudents(cmp);
StudentInfoTool.write2File(stus);
}
}