1. java中I/O的相关概念介绍:
Java中的读写操作基本分为两种形式:字节流(byte、8位、一般以Stream结尾)、字符流(char、16位、一般以Reader、Writer结尾);其中字符流只能以字符为单位读写文本型的文件,有时还需要考虑到编码的问题(默认编码为Unicode编码);字节流可以对本地的任何文件进行读写操作(如:图片、视频、文本文件等),无需考虑字符编码的问题。
(本文主要内容:基本输入输出流、标准输入输出流、标准输入输出流的重定向、文件的随机读写流、序列化和反序列化、临时文件的创建和销毁、更改图片大小及像素)
2.Java中有关I/O类示例解析
以copy文件为例,展示各种I/O相关类的用法。
/**
*1、利用FileInputStream/FileOutputStream 实现任意文件复制
* (以字节为单位进行数据的读写,可以读写任何数据文件。(比如图片、视频、文本等)不存在任何字符编码的问题)
*2、利用FileReader/FileWriter 实现文本文件的copy(以字符为单位进行读写数据、仅限于读写文本文件、存在字符编码的问题)
*
*3、BufferedReader/BufferedWriter 是字符处理流,对字符流进行缓冲和封装; 使得文件的读写更加便利。
*
*4、PrintWriter 类 可以方便的输出各种类型的数据、(int、char、byte、String) 并且不抛出IOExcetion
* (它具有PrintStream类的所有功能,不同之处是:可以封装字符流)
*
*5、DataOutputStream 和DataInputStream 类 能以一种以机器无关的方式,直接从底层字节输入流读取java基本类型的数据和String类型的数据。
*
* @author admin
*
*/
package oracle.hl.io;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class CopyFile {
public static void main(String[] args) {
CopyFile cf =new CopyFile ();
File fileName = new File("myfile.txt");
// cf.copyFileByByte("a.jpg");
// cf.copyFileByByteBuf("a.jpg");
// cf.copyFileByChar("myfile.txt");
// cf.copyFileByCharBuf("myfile.txt");
// cf.copyFileByBuffered(fileName);
cf.ReadWriterByDataStream(new File("file.data"));
}
/**
* 利用FileInputStream 和 FileOutputStream 类实现文件的复制
* 此方法利用无参的方法,read返回的是一个int型的整数; 真正有效的是低八位的数据,高24位全部用0填充;
* @param fileName
*/
public void copyFileByByte(String fileName){
try {
FileInputStream fis = new FileInputStream(fileName);
FileOutputStream fos = new FileOutputStream("b.jpg");
int read = fis.read();
while(read != -1){
fos.write(read);
read = fis.read();
}
fos.close();
fis.close();
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 利用FileInputStream 和 FileOutputStream 类实现文件的复制
* 利用一个byte数组来储存输入流读入的数据
* fos.write(buf, 0,read); 方法中的第三个参数是buf数组中有效数据的长度
* @param fileName
*/
public void copyFileByByteBuf(String fileName){
try {
FileInputStream fis = new FileInputStream(fileName);
FileOutputStream fos = new FileOutputStream("b.jpg");
byte buf [] = new byte[256];
int read = fis.read(buf);
while(read != -1){
// fos.write(buf);
fos.write(buf, 0,read);
read = fis.read(buf);
}
fos.close();
fis.close();
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 利用FileReader 和 FileWriter 类实现文件的复制(以字符为单位读取数据,因此只能操作文本型的文件)
* 此方法利用无参的方法,read返回的是一个int型的整数; 真正有效的是低16位的数据,高16位全部用0填充;
* @param fileName
*/
public void copyFileByChar(String fileName){
try {
FileReader fis = new FileReader(fileName);
FileWriter fos = new FileWriter("a.txt");
int read = fis.read();
while(read != -1){
fos.write(read);
read = fis.read();
}
fos.close();
fis.close();
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 利用FileReader 和 FileWriter 类实现文件的复制(以字符为单位读取数据,因此只能操作文本型的文件)
* 利用一个char数组来储存输入流读入的数据
* fos.write(c, 0,read); 方法中的第三个参数是c数组中有效数据的长度
* @param fileName
*/
public void copyFileByCharBuf(String fileName){
try {
FileReader fis = new FileReader(fileName);
FileWriter fos = new FileWriter("b.txt");
char [] c = new char[1024];
int read = fis.read(c);
while(read != -1){
// fos.write(c);
fos.write(c, 0, read); //第三个参数是c数组中有效数据的长度
read = fis.read(c);
}
fos.close();
fis.close();
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 利用FileReader 和 FileWriter 类实现文件的复制(以字符为单位读取数据,因此只能操作文本型的文件)
* BufferedReader 和 BufferedWriter类 作为处理流 缓冲和封装字符流。 使得读写操作更加方便。
* @param fileName
*/
public void copyFileByBuffered(File fileName){
try {
FileReader fis = new FileReader(fileName);
BufferedReader br = new BufferedReader(fis);
FileWriter fos = new FileWriter("c.txt");
// BufferedWriter bw = new BufferedWriter(fos);
PrintWriter bw = new PrintWriter(fileName);
String read = br.readLine();
while(read != null){
// bw.write(read);
// bw.newLine(); //相当于一个换行符,能够使读入的数据写入下一行。
bw.println(read); //此方法相当一以上两行代码的作用。
read = br.readLine();
}
bw.close();
br.close(); //要记得关闭各种输入输入出流。
fos.close();
fis.close();
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 利用 DataOutputStream 和DataInputStream 类 将制定类型的数据写入到指定文件中,然后再从文件将指定类型的数据读出来;
* 该类 能以一种以机器无关的方式,直接从底层字节输入流读取java基本类型的数据和String类型的数据。
* @param fileName
*/
public void ReadWriterByDataStream(File fileName){
try{
//将指定数据写入指定文件。
FileOutputStream fos = new FileOutputStream(fileName);
DataOutputStream dos = new DataOutputStream(fos);
dos.writeUTF("DataOutStream 类的测试!"); //向文件中写入一个字符串类型的数据
dos.write(22);
dos.writeBoolean(false);
//将指定文件的数据读出来,并显示在命令行
FileInputStream fis = new FileInputStream(fileName);
DataInputStream dis = new DataInputStream(fis);
System.out.println("String : "+dis.readUTF());
System.out.println("int : "+dis.read());
System.out.println("boolean : "+dis.readBoolean());
dos.close();
dis.close();
fos.close();
fis.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
3、几种特殊类的使用
1、标准输入、输出、错误输出的重定向。
/**
*1、 各种输入输出流的重定向(比如:将标准输入\输出\错误输出信息 流 重定向到一个文件。)
*
*2、 输入输出重定向的好处是能够将在控制台上的操作或显示转移到文件中去。
* @author admin
*
*/
package oracle.hl.io;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Date;
public class IORedirect {
public static void main(String[] args) {
IORedirect ior = new IORedirect();
File fileName =new File("data.txt");
ior.StandardInputRedirect(fileName);
}
/**
* 标准输入输出的重定向
* 此方法时间标准的键盘输入重定向到一个文件输入流,从文件中读入数据。
* @param fileName
*/
public void StandardInputRedirect(File fileName){
FileInputStream fis =null;
PrintStream out = null;
PrintStream errorOut = null;
try {
fis = new FileInputStream(fileName);
// 设置标准输入重定向到的流对象(文件输入流)
System.setIn(fis);
// FileOutStream 构造方法中true代表可以向文件中以追加的方式写入数据。
out = new PrintStream(new FileOutputStream("out.txt", true));
// 设置标准输出重定向到的流对象(文件输入流)
System.setOut(out);
errorOut = new PrintStream(new FileOutputStream("outerror.txt", true));
// 设置标准错误输出重定向到的流对象(文件输入流)
System.setErr(errorOut);
int num =0;
int avg = 0;
int totle = 0;
int i;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String st = br.readLine();
while(st != null){
i = Integer.parseInt(st);
totle += i;
num ++;
avg = totle/num;
System.out.println("num : "+num +" totle : "+ totle+" avg : "+avg);
st=br.readLine();
}
} catch (Exception e) {
// TODO: handle exception
System.err.println("出错的时间为:"+new Date());
System.err.print("出错信息为: ");
e.printStackTrace(System.err);
}finally{
try {
fis.close();
out.close();
errorOut.close();
} catch (Exception e2) {
// TODO: handle exception
System.err.println("出错的时间为:"+new Date());
System.err.print("出错信息为: ");
e2.printStackTrace(System.err);
}
}
}
}
2、RandomAccessFile类实现文件的随机读写操作
/**
* 实现用户成绩记录文件的随机读写操作
* 文件的随机读写操作
* 不受文输入流和输出流的限制 文件的读写操作用一个类就实现了。
* @author admin
*
*/
package oracle.hl.io;
import java.io.File;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
File file = null;
public static void main(String[] args) {
RandomAccessFileTest raft = new RandomAccessFileTest();
raft.init();
raft.record("qqqq", 20);
raft.record("aaaa", 10);
raft.record("qqqq", 10);
raft.record("werw", 34);
raft.record("aaaa", 12);
raft.printAllRecords();
}
private void init() {
// TODO Auto-generated method stub
//判断记录的文件是否已经存在,若不存在则创建新文件反之利用旧文件。
if(file == null){
file = new File("record.txt");
try {
file.createNewFile(); //创建新文件
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
public void record(String recordBreaker, int times){
try {
//创建并实例化一个RandomAccessFile对象,操作权限有 读 和 写
RandomAccessFile raf = new RandomAccessFile(file, "rw");
boolean flag = false;
//获取读写文件的指针的位置 和 文件的总长度。
while(raf.getFilePointer()<raf.length()){
String name = raf.readUTF();
if(recordBreaker.equals(name)){
raf.writeInt(times);
flag = true;
break;
}else {
//int型的数据占4个字节,所以当没有匹配到用户时可以直接跳过4个字节 ; 以检测下一条记录。
raf.skipBytes(4);
}
}
//如果是新用户的话就直接在文件最后追加一条新纪录
if(!flag){
raf.writeUTF(recordBreaker);
raf.writeInt(times);
}
raf.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//便利记录文件的所有用户的成绩
public void printAllRecords(){
try {
RandomAccessFile raf = new RandomAccessFile(file, "r");
while(raf.getFilePointer()<raf.length()){
String name = raf.readUTF();
int times = raf.readInt();
System.out.println("name : "+name+"tiems : "+times);
}
raf.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
3、临时文件的创建和删除
/**
* 临时文件的创建和删除
*
* delete()
删除此抽象路径名表示的文件或目录。
deleteOnExit()
在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。
可以这样理解,JVM是Java程序运行的环境,当运行test()方法时,JVM随之启动,它也有自己的生命周期。
当调用delete()方法直接删除文件时,不用判断文件是否存在,一经调用立即执行;
而调用deleteOnExit()方法时,我的理解是只是相当于对deleteOnExit()作一个声明,当程序运行结束
,JVM终止时才真正调用deleteOnExit()方法实现删除操作。
* @author admin
*
*/
package oracle.hl.io;
import java.io.File;
import java.io.IOException;
public class CreateTempFileTest {
public static void main(String[] args) {
try {
File tempFile =null;
for(int i=0; i<10; i++){
//不指定路径时默认存储到当前操作系统的临时文件夹
tempFile =File.createTempFile("111myTempFile", ".txt");
System.out.println(tempFile.getAbsolutePath());
tempFile.deleteOnExit(); //删除临时文件
}
//主线程休眠10000毫秒,以观察临时文件的创建和删除 机制
Thread.sleep(10000);
System.out.println("end");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4、对象的序列化和反序列化操作(ObjectOutputStream、ObjectOutputStream)
//将要被序列化的Student类
package oracle.hl.io;
//import java.io.Externalizable;
import java.io.Serializable;
//想要被序列化的类必须实现Serializable接口或实现Externalizable接口
public class Student implements Serializable {
private String name;
private int age;
private transient String sex; //被关键字transientd的属性 不能被序列化 静态属性也不能被实例化
public Student(String name, int age, String sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
序列化和反序列化的实现类
/**
* Object 的序列化和反序列化功能。
*
* 被序列化的对象的对应的类必须实现Serializable 或Externalizable接口
* @author admin
*
*/
package oracle.hl.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectIOStream {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream("Object.data");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(new Student("张三", 20, "男"));
oos.writeObject(new Student("李四", 19, "男"));
oos.close();
FileInputStream fis = new FileInputStream("Object.data");
ObjectInputStream ois = new ObjectInputStream(fis);
Student s1 = (Student) ois.readObject();
Student s2 = (Student) ois.readObject();
System.out.println(s1.toString());
System.out.println(s2.toString());
ois.close();
} catch (Exception e) {
// TODO: handle exception
}
}
}
5、改变图片像素及大小
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.imageio.ImageIO;
public class test1 {
/**
* 改变图片的大小到宽为size,然后高随着宽等比例变化
* @param is 上传的图片的输入流
* @param os 改变了图片的大小后,把图片的流输出到目标OutputStream
* @param size 新图片的宽
* @param format 新图片的格式
* @throws IOException
*/
public static void resizeImage(InputStream is, OutputStream os, int size, String format) throws IOException {
BufferedImage prevImage = ImageIO.read(is);
double width = prevImage.getWidth();
double height = prevImage.getHeight();
System.out.println(width + " "+ height);
double percent = size/width;
int newWidth = (int)(width * percent);
int newHeight = (int)(height * percent);
//新建图片缓冲去对象
BufferedImage image = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_BGR);
//根据image获得绘图对象
Graphics graphics = image.createGraphics();
//重新绘图
graphics.drawImage(prevImage, 0, 0, newWidth, newHeight, null);
ImageIO.write(image, format, os);
os.flush();
is.close();
os.close();
}
public static void main(String args[]){
try {
FileInputStream in = new FileInputStream("D:\\101.jpg");
FileOutputStream out = new FileOutputStream("111.png");
resizeImage(in, out, 840, "png");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4、I/O总结
实际上字节流操作时本身不会用到缓冲区(内存),是对文件本身进行操作的,而字符流在操作时使用了缓冲区(内存),通过缓冲区在对文件进行操作。
使用字符流还是字节流好?所有文件在硬盘或传输时都是以字节方式进行的,包括图片等都是以字节方式存储的,而字符是只有在内存中才会形成,所以在开发中,字节流使用较为广泛。