Java中IO操作主要是指使用Java进行输入,输出操作,Java中所有的操作类都存放在Java.io包中,在使用时需要导入此包。
在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable.掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识了。
一、java.io包中流的分类
Java.io包中定义了多个流类型类实现输入输出的功能,从不同的角度可以分为:
~按照数据流方向的不同可以分为输入流和输出流。
~按照按照数据处理单位的不同可以分为字节流和字符流。
J2Sdk所提供的所有流类型位于Java.io包内部分别都继承以下四种抽象流类型,如图:
二、Java中IO流的体系结构
下面根据数据处理单位不同来分析Java中流的体系结构
如图:
三、IO流中5大类的特征
1、InputStream
InputStream 为字节输入流,它本身为一个抽象类,必须依靠其子类实现各种功能,此抽象类是表示字节输入流的所有类的超类。 继承自InputStream 的流都是向程序中输入数据的,且数据单位为字节(8bit);下面是InputStream所属的子类:
~ FileInputStream :
从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream
用于读取诸如图像数据之类的原始字节流。
package IO_TEST;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class File_test {
public static void createFile(){
File f = new File("/Users/zhengchao/cctv/File_test.txt");
try {
f.createNewFile();
} catch (IOException ex) {
Logger.getLogger(File_test.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println("文件名 "+f.getName()); //返回由此抽象路径名表示的文件或目录的名称。
System.out.println("文件父目录字符串 "+f.getParent());// 返回此抽象路径名父目录的路径名字符串
//f.delete(); //删除创建的文件
}
public static void main(String[] args){
createFile();
}
}
package IO_TEST;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public class InputStream_test {
public static void main(String[] args){
int count = 0;
InputStream streamReader = null;//文件输入流
try {
streamReader = new FileInputStream(new File("/Users/zhengchao/cctv/File_test.txt"));
while(streamReader.read()!=-1) { //读取文件字节,并递增指针到下一个字节
count++;
}
System.out.println("---长度是: "+count+" 字节");
} catch (IOException ex) {
Logger.getLogger(InputStream_test.class.getName()).log(Level.SEVERE, null, ex);
}finally{
try{
streamReader.close();//FileInputStream是有缓冲区的,所以用完之后必须关闭,否则可能导致内存占满,数据丢失。
}catch (IOException e) {
}
}
}
}
2、OutputStream
为字节输出流,是整个IO包中字节输出流的最大父类,OutputStream类也是一个抽象类,要使用此类必须通过子类实例化对象。
其子类有:
示例:
package IO_TEST;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class OutputStream_test {
public static void main(String[] args) {
byte[] buffer=new byte[512]; //一次取出的字节数大小,缓冲区大小
int numberRead=0;
InputStream input=null;
OutputStream out =null;
try {
input = new FileInputStream("/Users/zhengchao/cctv/File_test.txt");
out = new FileOutputStream("/Users/zhengchao/cctv/File_test_out.txt"); //如果文件不存在会自动创建
while ((numberRead=input.read(buffer))!=-1) { //numberRead的目的在于防止最后一次读取的字节小于buffer长度,
out.write(buffer, 0, numberRead); //否则会自动被填充0
}
} catch (final IOException e) {
e.printStackTrace();
}finally{
try {
input.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package IO_TEST;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ObjetStream_test {
public static void main(String[] args) {
ObjectOutputStream objectwriter=null;
ObjectInputStream objectreader=null;
try {
objectwriter=new ObjectOutputStream(new FileOutputStream("/Users/zhengchao/cctv/File_objectwriter_out.txt"));
objectwriter.writeObject(new Student("gg", 22));
objectwriter.writeObject(new Student("tt", 18));
objectwriter.writeObject(new Student("rr", 17));
objectreader=new ObjectInputStream(new FileInputStream("/Users/zhengchao/cctv/File_objectwriter_out.txt"));
for (int i = 0; i < 3; i++) {
System.out.println(objectreader.readObject());
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}finally{
try {
objectreader.close();
objectwriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class Student implements Serializable{
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
Student类必须实现Serializable接口,并重写toString()方法,否则
objectwriter.writeObject(new Student("gg", 22)); 执行会报错。
package IO_TEST;
import java.io.*;
import java.util.Enumeration;
import java.util.Vector;
public class NewClass {
/**
* @param args
* SequenceInputStream合并流,将与之相连接的流集组合成一个输入流并从第一个输入流开始读取,
* 直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
* 合并流的作用是将多个源合并合一个源。可接收枚举类所封闭的多个字节流对象。
*/
public static void main(String[] args) {
doSequence();
}
private static void doSequence() {
SequenceInputStream sis = null; // 创建一个合并流的对象
BufferedOutputStream bos = null; // 创建输出流。
try {
// 构建流集合。
Vector<InputStream> vector = new Vector<InputStream>();
vector.addElement(new FileInputStream("/Users/zhengchao/cctv/File_1.txt"));
vector.addElement(new FileInputStream("/Users/zhengchao/cctv/File_1.txt"));
Enumeration<InputStream> e = vector.elements();
sis = new SequenceInputStream(e);
bos = new BufferedOutputStream(new FileOutputStream("/Users/zhengchao/cctv/File_OUT.txt"));
// 读写数据
byte[] buf = new byte[1024];
int len = 0;
while ((len = sis.read(buf)) != -1) {
bos.write(buf, 0, len);
bos.flush();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
try {
if (sis != null)
sis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bos != null)
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3、Writer
写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。 其子类如下:
~BufferedWriter :
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。
4、Reader
用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。 子类有:
import java.io.*;
class IODemo {
public static void main(String[] args){
try {
//使用BufferedReader和BufferedWriter进行文件复制(操作的是字符,以行为单位读入字符)
FileReader fr=new FileReader("a.txt");
BufferedReader br=new BufferedReader(fr);
FileWriter fw=new FileWriter("d.txt");
BufferedWriter bw=new BufferedWriter(fw);
String s=br.readLine();
while(null!=s) {
bw.write(s);
//由于BufferedReader的rendLIne()是不读入换行符的,所以写入换行时须用newLine()方法
bw.newLine();
//read=fis.read(b);
s=br.readLine();
}
br.close();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
管道流:
PipedInputStream
void connect(PipedOutputStream src)
使此管道输入流连接到管道输出流 src
PipedOutputStream
void connect(PipedInputStream snk)
在JDK我们看到PipedInputStream中有管道缓冲区,用来接收数据
管道流内部在实现时还有大量的对同步数据的处理
管道输出流和管道输入流执行时不能互相阻塞,所以一般要开启独立线程分别执行
顺便复习了多线程操作
[示例]:管道流
*/
import java.io.*;
class Demo
{
public static void main(String[] args) throws Exception
{
PipedInputStream pin = new PipedInputStream();
PipedOutputStream pout = new PipedOutputStream();
pin.connect(pout); //输入流与输出流连接
ReadThread readTh = new ReadThread(pin);
WriteThread writeTh = new WriteThread(pout);
new Thread(readTh).start();
new Thread(writeTh).start();
}
public static void sop(Object obj) //打印
{
System.out.println(obj);
}
}
class ReadThread implements Runnable
{
private PipedInputStream pin;
ReadThread(PipedInputStream pin) //
{
this.pin=pin;
}
public void run() //由于必须要覆盖run方法,所以这里不能抛,只能try
{
try
{
sop("R:读取前没有数据,阻塞中...等待数据传过来再输出到控制台...");
byte[] buf = new byte[1024];
int len = pin.read(buf); //read阻塞
sop("R:读取数据成功,阻塞解除...");
String s= new String(buf,0,len);
sop(s); //将读取的数据流用字符串以字符串打印出来
pin.close();
}
catch(Exception e)
{
throw new RuntimeException("R:管道读取流失败!");
}
}
public static void sop(Object obj) //打印
{
System.out.println(obj);
}
}
class WriteThread implements Runnable
{
private PipedOutputStream pout;
WriteThread(PipedOutputStream pout)
{
this.pout= pout;
}
public void run()
{
try
{
sop("W:开始将数据写入:但等个5秒让我们观察...");
Thread.sleep(5000); //释放cpu执行权5秒
pout.write("W: writePiped 数据...".getBytes()); //管道输出流
pout.close();
}
catch(Exception e)
{
throw new RuntimeException("W:WriteThread写入失败...");
}
}
public static void sop(Object obj) //打印
{
System.out.println(obj);
}
}
R:读取前没有数据,阻塞中...等待数据传过来再输出到控制台...
W:开始将数据写入:但等个5秒让我们观察...
R:读取数据成功,阻塞解除...
W: writePiped 数据...
@GET
@Path("/download/settleLoan")
public Response downloanSettleLoan(@QueryParam("loanStatus") LoanStatus status) throws IOException {
String contentDisposition = "attachment; filename*=UTF-8''" + URLEncoder.encode(status.getKey().toString().concat("_借款列表"), "UTF-8") + ".csv";
final PipedOutputStream output = new PipedOutputStream();
PipedInputStream input = new PipedInputStream(output);
Runnable writer = new Runnable() {
@Override
public void run() {
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(output, "GBK"));) {
StringBuilder sb = new StringBuilder();
sb.append("投标数").append(CSV_SEPERATOR)
.append("投标金额(元)");
bw.append(sb.toString());
bw.newLine();
for (int i = 0;i<10; i++ ) {
sb = new StringBuilder();
sb.append(String.valueOf(i)).append(CSV_SEPERATOR)
.append(String.valueOf(i*100000));
bw.append(sb.toString());
bw.newLine();
}
bw.flush();
} catch (Exception ex) {
logger.error("Exception happened when write CSV for user list.", ex);
}
}
};
Thread thread = new Thread(writer);
thread.start();
return Response.ok(input, "text/csv").encoding("GBK").header("Content-Disposition", contentDisposition).build();
}
我们看这段代码大的知识点:
BufferedWriter类封装了OutputStreamWriter类;
BufferedReader类封装了InputStreamReader类;
封装格式:
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(System.out));
BufferedReader in= new BufferedReader(new InputStreamReader(System.in);
1:字节输入流转换为字符输入流:
InputStreamReader是字节流向字符流的桥梁,它使用指定的charset读取字节并将其解码为字符,它使用的字符集可以由名称指定或显示给定。根据InputStream的实例创建InputStreamReader的方法有4种:
InputStreamReader(InputStream in)//根据默认字符集创建
InputStreamReader(InputStream in,Charset cs)//使用给定字符集创建
InputStreamReader(InputStream in,CharsetDecoder dec)//使用给定字符集解码器创建
InputStreamReader(InputStream in,String charsetName)//使用指定字符集创建
2:字节输出流转换为字符输出流
OutputStreamWriter是字符流通向字节流的桥梁,它使用指定的charset将要写入流中的字符编码成字节,它使用的字符集可以由名称指定或显示给定,否则将接受默认的字符集:
根据根据InputStream的实例创建OutputStreamWriter的方法有4种:
OutputStreamWriter(outputstream out)//根据默认的字符集创建
OutputStreamWriter(outputstream out,charset cs)//使用给定的字符集创建
OutputStreamWriter(outputstream out,charsetDecoder dec)//使用组定字符集创建
OutputStreamWriter(outputstream out,String charsetName)//使用指定字符集创建
在Java.io包中操作文件内容的主要有两大类:字节流、字符流,两类都分为输入和输出操作。在字节流中输出数据主要是使用OutputStream完成,输入使的是InputStream,在字符流中输出主要是使用Writer类完成,输入流主要使用Reader类完成。(这四个都是抽象类)
java中提供了专用于输入输出功能的包Java.io,其中包括:
InputStream,OutputStream,Reader,Writer
InputStream 和OutputStream,两个是为字节流设计的,主要用来处理字节或二进制对象,
Reader和 Writer.两个是为字符流(一个字符占两个字节)设计的,主要用来处理字符或字符串.
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点
所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列
字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串; 2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以
字节流是最基本的,所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的 但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化 这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联 在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的
==================我们还可以看到:============
Reader类的read()方法返回类型为int :作为整数读取的字符(占两个字节共16位),范围在 0 到 65535 之间 (0x00-0xffff),如果已到达流的末尾,则返回 -1
inputStream的read()虽然也返回int,但由于此类是面向字节流的,一个字节占8个位,所以返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。因此对于不能用0-255来表示的值就得用字符流来读取!比如说汉字.