1.IO流的概念和作用
程序运行在内存和CPU所构成的资源里,内存和CPU永远都是运行的程序的家。回想一下,语句就是对CPU的操作,而变量和new对象是对内存的操作,CPU的重要意义无需多说,而内存的重要性是,我们的程序根本就没办法真正离开内存。
可是有很多东西需要程序来操作,它们不在内存里,比如键盘、硬盘、打印机、网络等,那么该如何操作这些东西呢?
程序运行在内存里,我们需要一种模式来和内存外的东西打交道,我们管这样的操作叫IO流,I是input(输入),O是Output(输出),这就有了相对什么的入和出,要建立一个概念,我们就是程序,将我们的反应表现赋予给程序,而程序在内存里,这样看来内存里和内存外就构成了入和出的方向,到内存里就是入,到内存外就是出。
IO流需要三步操作:
第一步,定位。我们要知道操作的东西放在什么地方,比如对硬盘,我们要知道在哪个盘上,哪个目录里的哪个文件;再比如对网络,我们要知道哪个网络地址的哪个端口。
第二步,建立管道(见下图)。如果假设要操作的东西是一个水桶,或者管它叫做数据源,现在需要用一个水管连接水桶和内存,这个水管是有方向的,I就是向内存里流水的水管,O就是向内存外流水的水管。
第三步,操作管道。我们的程序还在内存里,去操作刚刚建立好的那个管道。
输入与输出:
什么是输入:
输入是一个从外界到程序的方向,通常我们需要“读取”外界的数据时,使用输入。所以输入是用来读取数据的。
什么是输出:
输出是一个从程序发送到外界的方向,通常我们需要“写出”数据到外界,使用输出,所以输出是用来写出数据的。
节点流与处理流
按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类。
节点流:可以从(或向)一个特定的地方(节点)读写数据。
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。
2.IO流的分类
根据处理数据类型的不同分为:字符流和字节流根据数据流向不同分为:输入流和输出流
1.字符流和字节流
字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流和字符流的区别:
读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
输入流和输出流
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
3.IO流对象
3.1 IO流类的层次结构
3.2 字节流
InputStream 是所有的输入字节流的父类,它是一个抽象类。OutputStream 是所有的输出字节流的父类,它是一个抽象类。
例子1:
用FileInputStream类读取文件内容,test1.txt文件中的内容是“0123456789”,将打印到控制台上。
public static void main(String args[]){
File inFile = new File("c:/work/test1.txt");
FileInputStream fis = null;
try {
fis = new FileInputStream(inFile);
int length = fis.available();
for(int i=0; i<length; i++){
System.out.print((char)fis.read());
}
} catch (Exception e) {
e.printStackTrace();
} finally{
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例子2:
用FileInputStream和FileOutputStream复制文件。
public static void main(String args[]){
File inFile = new File("c:/work/test.txt");
File outFile = new File("c:/work/test2.txt");
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(inFile);
fos = new FileOutputStream(outFile);
int length = fis.available();
for(int i=0; i<length; i++){
fos.write(fis.read());
}
} catch (Exception e) {
e.printStackTrace();
}try {
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
例子3:
几种复制文件的方式效率比较
1.使用字节流逐字节复制文件,效率最差。
public static void copyFile1(){
long time = System.currentTimeMillis() ;
File inFile = new File("c:/work/src.zip");
File outFile = new File("c:/work/src1.zip");
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(inFile);
fos = new FileOutputStream(outFile);
int length = fis.available();
//循环 20777984次
for(int i=0; i<length; i++){
fos.write(fis.read());
}
time = System.currentTimeMillis() - time ;
System.out.println("耗时"+time+"毫秒") ;
//246698毫秒 4分钟
} catch (Exception e) {
e.printStackTrace();
}try {
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2.使用byte字节数组做临时的缓存区复制文件,效率较高。
public static void copyFile2(){
long time = System.currentTimeMillis() ;
File inFile = new File("c:/work/src.zip");
File outFile = new File("c:/work/src2.zip");
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(inFile);
fos = new FileOutputStream(outFile);
//建议使用2的n次方,这样计算机的处理效率相对会高一些。
//这里用byte数组纯粹的申请内存
byte[] tmp = new byte[8192];
int length = fis.available()/8192;
for(int i=0; i<length; i++){
fis.read(tmp);
fos.write(tmp);
}
int size = fis.read(tmp);
//第一个参数是byte数组形成的临时缓冲区
//第二个参数是从数组的哪里开始向文件写
//第三个参数是写多少
fos.write(tmp,0,size);
time = System.currentTimeMillis() - time ;
System.out.println("耗时"+time+"毫秒") ;
//100毫秒左右
} catch (Exception e) {
e.printStackTrace();
}try {
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
3.使用缓冲字节流BufferedInputStream和BufferedOutputStream复制文件,效率较高。
public static void copyFile3(){
long time = System.currentTimeMillis() ;
File inFile = new File("c:/work/src.zip");
File outFile = new File("c:/work/src3.zip");
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
fis = new FileInputStream(inFile);
bis = new BufferedInputStream(fis);
fos = new FileOutputStream(outFile);
bos = new BufferedOutputStream(fos);
int length = bis.available();
for(int i=0; i<length; i++){
bos.write(bis.read());
}
time = System.currentTimeMillis() - time ;
System.out.println("耗时"+time+"毫秒") ;
//1300毫秒左右
} catch (Exception e) {
e.printStackTrace();
}try {
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
4.同时使用缓冲字节流和byte数组复制文件,效率最高。
public static void copyFile4(){
long time = System.currentTimeMillis() ;
File inFile = new File("c:/work/src.zip");
File outFile = new File("c:/work/src3.zip");
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
fis = new FileInputStream(inFile);
bis = new BufferedInputStream(fis);
fos = new FileOutputStream(outFile);
bos = new BufferedOutputStream(fos);
byte[] tmp = new byte[8192];
int length = fis.available()/8192;
for(int i=0; i<length; i++){
bis.read(tmp);
bos.write(tmp);
}
int size = bis.read(tmp);
bos.write(tmp,0,size);
time = System.currentTimeMillis() - time ;
System.out.println("耗时"+time+"毫秒") ;
//90毫秒左右
} catch (Exception e) {
e.printStackTrace();
}try {
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
3.3 字符流
字符流类都是Reader和Writer的子类,像字节流一样,也有特定的针对文件I/O的字符流:FileReader和FileWriter。字符流就是专门处理字符的流。什么是字符?狭义的讲,就是ASCII码。也可以这么理解,只要记事本能够处理的文件,字符流就能处理。我们知道文本文件在整个计算机里所占的比例并不是太高,但是被程序员处理的频率却很高,所以Java专门提供了字符流,为了能够更加方便的处理文本文件。如果是你,你希望字符流的类里增加什么方法,我想自然读到的东西不应该再强转成char,作为文本文件,你是不是希望能够一次读一行文字,这样就能直接存到String里,而不用一个字一个字地读了。
一次读一行内容,Java提供了带缓冲区的流BufferedReader,BufferedReader将字符攒了起来,在遇到换行的时候,直接提供一行的String给你。
例子1:
使用BufferedReader逐行读文本文件内容。
public static void main(String[] args) {
File inFile = new File("c:/work/test10.txt");
try{
FileReader fr = new FileReader(inFile);
/**
* 带缓冲区的流BufferedReader,BufferedReader将字符攒了起来,
* 在遇到换行的时候,直接提供一行的String给你。
*/
BufferedReader br = new BufferedReader(fr);
while(br.ready()){
String str = br.readLine();
System.out.println(str);
}
} catch(Exception e){
e.printStackTrace();
} finally {
}
}
使用BufferedReader能逐行的读文件内容,那么逐行的向文件写入内容是不是用BufferedWriter,没错,BufferedWriter是带缓冲区的字符输出流,但是我们通常用另一个类PrintWriter,这是因为BufferedWriter更专注于对缓冲区的管理能力,而PrintWriter在此基础上针对输出格式进行了处理。我们知道Java是跨平台的,这样就面临着一个问题,在不同的平台上,虽然字符的定义通常都会遵守国际标准,但是有些控制字符,比如回车,换行之类的可能会有差别,如果使用PrintWriter,它会适应不同的系统平台。
例子2:
public static void main(String[] args) {
File inFile = new File("c:/work/test10.txt");
File outFile = new File("c:/work/test11.txt");
try{
FileReader fr = new FileReader(inFile);
BufferedReader br = new BufferedReader(fr);
FileWriter fw = new FileWriter(outFile);
/**
* FileReader对应的输出流是FileWriter,这个应该不会猜错,
* 也会猜BufferedReader对应的是BufferedWriter,没错,BufferedWriter是
* 带缓冲区的字符输出流,但是我们通常用另一个类PrintWriter,这是因为BufferedWriter
* 更专注于对缓冲区的管理能力,而PrintWriter在此基础上针对输出格式进行了处理。
* 我们知道Java是跨平台的,这样就面临着一个问题,在不同的平台上,虽然字符的定义通常都会遵守国际标准,
* 但是有些控制字符,比如回车,换行之类的可能会有差别,如果使用PrintWriter,它会适应不同的系统平台。
* BufferedReader
*/
//BufferedWriter bw = new BufferedWriter(fw);
PrintWriter pw = new PrintWriter(fw,true);
while(br.ready()){
pw.println(br.readLine());
}
//pw.close();
} catch(Exception e){
e.printStackTrace();
}
}
3.4 字节流与字符流的转换
Java中字符流与字节流的转换通过InputStreamReader和OutputStreamWriter两个类来实现。
InputStreamReader和OutputStreamWriter是字节流到字符流的桥梁
何时使用转换流?
当字节和字符之间有转换动作时;
流操作的数据需要编码或解码时。
这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。
例子1:
用字节流与字符流转换读取文件内容。
public static void readFile(){
File file = new File("c:/work/test12.txt");
try{
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis);
//FileReader
BufferedReader br = new BufferedReader(isr);
while(br.ready()){
System.out.println(br.readLine());
}
} catch(Exception e){
e.printStackTrace();
}
}
例子2:
使用字节流与字符流转换将文本写入文件。
public static void writerToFile(){
String str = "百度一下,你就知道";
File file = new File("c:/work/test13.txt");
try{
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter otw = new OutputStreamWriter(fos);
//第二个参数
PrintWriter pw = new PrintWriter(otw,true);
pw.println(str);
//pw.close();
} catch(Exception e){
e.printStackTrace();
}
}
3.5 扫描器
扫描器由Scanner对象实现,使用分隔符模式将输入分解为独立的标记,默认情况下该分隔符模式与空白(包括空格、tab、和行分隔符)匹配。
1.通过扫描器读取文件内容
下面通过一个例子可以了解扫描器是如何工作的,程序从文件test12.txt中读取内容并按行输出到屏幕上。
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("c:/work/test12.txt");
Scanner s = new Scanner(fis);
while (s.hasNext()) {
System.out.println(s.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
2.扫描控制台的输入
通过new Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。如果要获取输入的内容,则只需要调用Scanner的nextLine()方法即可。
下面的例子在运行之后,在控制台输入任意字符,按回车键,输入的字符将会再次输出到控制台。
public static void main(String[] args) {
try {
Scanner s = new Scanner(System.in);
System.out.println("请输入字符串:");
while (s.hasNext()) {
System.out.println(s.nextLine());
}
} catch (Exception e) {
e.printStackTrace();
}
}
3.6 一个综合的例子
使用Socket,首先编写一个服务端程序,服务端启动时,在某个端口监听,当客户端有信息发送过来时,打印在服务端的控制台上。
其次,编写一个客户端程序,在程序启动时,连接服务端。客户端可以在控制台输入一些信息,输入信息后敲回车键,信息将会发送给服务端,在服务端的控制台上将会打印出收到的信息。
运行时先运行服务端程序,再运行客户端程序。
服务端程序:
public class MyServer {
static ServerSocket ss = null;
public static void main(String[] args) {
try {
ss = new ServerSocket(9001);
System.out.println("开始监听...");
Socket s = ss.accept();
InputStream is = s.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// OutputStream os = s.getOutputStream();
// OutputStreamWriter osr = new OutputStreamWriter(os);
// PrintWriter pw = new PrintWriter(osr,true);
while(true){
System.out.println(sdf.format(System.currentTimeMillis())+" "+br.readLine());
// pw.println("服务器收到了");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null != ss){
try {
System.out.println("关闭Server");
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端程序:
public class MyClient {
public static void main(String[] args) {
try{
Socket s = new Socket("127.0.0.1",9001);
OutputStream os = s.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
PrintWriter pw = new PrintWriter(osw,true);
// InputStream is = s.getInputStream();
// InputStreamReader isr = new InputStreamReader(is);
// BufferedReader br = new BufferedReader(isr);
Scanner scan = new Scanner(System.in);
while(scan.hasNext()){
pw.println(scan.nextLine());
// System.out.println(br.readLine());
}
//os.close();
} catch(Exception e){
e.printStackTrace();
}
}
}