javaIO流
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javaIO流相关的知识,希望对你有一定的参考价值。
IO流:
IO流是用来处理设备之间的数据传输的。
java对数据的操作是通过流的方式。
java用于操作流的对象都在IO包中。
流按操作的数据分为两种:字符流和字节流。
字符流的由来:字符流其实就是字节流读取文字字节数据后,不直接操作而是先查指定的编码表, 获取对应的文字。再对这个文字进行操作。
简单说,就是字符流=字节流+编码表。
字节流的抽象基类(就是顶层父类):InputStream,OutputStream
字符流的抽象基类:Reader,Writer
这些体系的子类都以父类名作为后缀,而子类名的前缀就是该对象的功能。
就从熟悉的文字开始学习,也就是字符流:
//需求:将一些文字存入到硬盘中。(记住:如果要操作文字,优先考虑字符流,而且要将文字存到硬盘上,要用输出流——writer)
// 硬盘的数据基本体现是文件。所以就找到了FileWriter.
package day20;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) throws IOException {
/*创建一个可以往文件中写入字符数据的字符输出流对象。
* 既然是往一个文件中写入文字数据,那么在创建对象时就必须明确该文件(用于存储数据的目的地)
* 如果文件不存在就会自动创建。如果存在就会被覆盖。
* */
FileWriter fw=new FileWriter("demo.txt");
/*
* 调用Writer对象中的write(String)方法写入数据。
* 这样做后数据就写入到了临时存储缓冲区中。
* */
fw.write("abcde");
/*
* 要将数据存储到目的地,还需要进行刷新操作。
* */
fw.flush();
/*
* 关闭流,关闭资源。在关闭前会调用flush刷新缓冲区的数据到达目的地。
* */
fw.close();
//fw.write("haha"); //这句话就会抛出异常,流都关闭了,不能再往进写了。
/* flush与close区别:
* flush可以刷新多次,close只能一次
* flush相当于文件写入新数据保存后,文件没关闭,刷新一下,就会看到新的内容,然后还可以继续写,可以反复这样做,重复多次。
* close会自动调用flush就相当于你关闭文件时,系统会提示你文件未保存是否要保存一样,点了保存,但是如果要查看,就必须再次打开文件,这就相当于重新打开了一个流。所以说close只能刷新一次。
* */
/* 两个小问题:
* 1.换行:
* 如果要在文件中第一行写abcd 第二行写haha 怎么操作呢?
* 方法一:windows系统的换行\r\n
* \n是不行的,记事本是windows操作软件,不认识这个。
* fw.write("abcde\r\nhaha");
* 方法二:unix系统的换行。先要声明LINE_SEPARATOR
* private static final String LINE_SEPARATOR = System.getProperty("line.separator");
* fw.write("abcde"+LINE_SEPARATOR+"haha");
* 2.续写:
* 如果写好了数据之后,又想要添加新的内容,还不想覆盖原来的,怎么操作?
* 看下API里FileWriter的构造函数,可以知道在创建对象时,加上true就行了。
* FileWriter fw=new FileWriter("demo.txt",true);
* */
}
}
IO异常的处理方式:
package day20;
import java.io.FileWriter;
import java.io.IOException;
/*
* IO异常处理:
* 1.在try外面创建对象,里面初始化。
* 2.close放在finally中,而且记得要判断对象是否为空。
* 如果创建的文件地址不存在,就不需要close。
* */
public class IOExceptionDemo {
public static void main(String[] args) {
FileWriter fw=null;
try {
fw=new FileWriter("demo.txt");
fw.write("abcde");
}catch (Exception e) {
System.out.println(e.toString());
}finally{
if(fw!=null)
try {
fw.close(); //fw对象在try代码块中创建的,所以这样会异常。
} catch (IOException e) {
throw new RuntimeException("关闭失败");
}
}
}
}
//需求:读取一个文本文件,将读取到的字符打印。和上面Writer类似,找到了FileReader。
package day20;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) throws IOException {
/*
* 创建读取字符数据的流对象。
* 在创建读取流对象时,必须明确被读取的文件,一定要确定该文件是存在的。
* 用一个读取流关联一个已存在的文件。
* */
FileReader fr=new FileReader("demo.txt");
//用read方法读取文件中的内容。返回的是int
int ch=0;
while((ch=fr.read())!=-1){
System.out.println((char)ch);
}
/*
* int ch=fr.read();
* System.out.println(ch);
* int ch1=fr.read();
* System.out.println(ch1);
* fr.close(); //如果文件读完了,就返回-1.不过这样写太麻烦了。
* */
/* 使用read(char[])读取文本文件数据。
* char[] buf=new char[3];
* int len=0;
* while((len=fr.read(buf))!=-1){
* System.out.println(new String(buf,0,len));
* }
* */
}
}
了解几个类:
System类:
System类中的方法都是静态的。
常用方法:
currentTimeMillis();获取当前时间的毫秒值。两个一减就能算出程序运行的时间。
getProperty(); 确定当前的系统属性。
properties集合是map的子集,其中存储的都是String类型的键和值。键是固定的,不同机器属性不同,所以键对应的值就不同。
最好使用它自己的存储和取出的方法来完成元素的操作。比如换行操作:通常我们知道的是unix系统中的\n,windows系统就是\r\n,其他系统可能还不一样,java最牛的就是跨平台性,怎么解决呢?
其实系统属性中就有换行的操作,所以只需要调用getProperty()方法中的line.separator键,就可获取他的值,就是换行操作。
例如:System.out.println("hello"+System.getProperty("line.separator")+"world");
这句代码在任何系统都可以获取该系统的回车符,从而实现换行操作。很常用,通常会把他定义成常量来使用。
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
System.out.println("hello"+LINE_SEPARATOR+"world");
setProperty("key","value"); 给系统设置一些属性信息,这些信息是全局的,其他程序都可以使用。
Runtime类:
没有构造方法摘要,这说明该类不可以创建对象。
又发现还有非静态的方法,说明该类应该提供静态的返回该类对象的方法。
而且发现静态方法只有一个,说明runtime类使用了单例设计模式。
package day20;
import java.io.IOException;
public class RunTimeDemo {
public static void main(String[] args) throws IOException {
Runtime r=Runtime.getRuntime(); //返回与当前 Java 应用程序相关的运行时对象。
//execute:执行 xxx.exe
r.exec("notepad.exe"); //开启执行文件——记事本。
//先在当前文件目录找notepad,没找到,然后去path找,找到了。
//如果执行qq就异常了,因为qq不在path目录下,找不到文件。
Process p=r.exec("notepad.exe");
p.destroy(); //结束子进程。只能结束r.exec()开启的。
}
}
Math类:
math提供了操作数学运算的方法。都是静态的。
常用方法:
abs(); 返回绝对值
ceil(); 返回大于参数的最小整数
floor(); 返回小于参数的最大整数
round(); 返回四舍五入的整数。
pow(a,b); 返回a的b次方
random(); 产生一个大于0小于1的double类型的值。利用这个可以产生随机数。
比如要产生1-10的随机数:Math.ceil(Math.random()*10);
Date类:
after(date) 测试此日期是否在指定日期之后。
bofore(date) 测试此日期是否在指定日期之前。
compareTo(date) 比较两个日期是否相等。
package day20;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateDemo {
public static void main(String[] args) {
long time=System.currentTimeMillis();
System.out.println(time);
Date date=new Date(); //将当前日期时间封装成Date对象
System.out.println(date); //输出结果就是当前时间Sun May 21 17:14:18 CST 2017
Date date2=new Date(time); //将time代表的毫秒值封装成Date对象
System.out.println(date2); //输出的是time对应的日期时间。
/*日期对象和毫秒值之间的转换
*
* 毫秒值转换成日期:
* 因为可以通过Date对象的方法对该日期的各个字段(年月日)进行操作。
* 1.通过date对象的构造方法new Date(timeMillis)。比如上面例子
* 2.也可以通过setTime设置。
* 日期对象转换成毫秒:
* 因为可以通过具体的数值进行运算。
* 1.通过getTime方法
*/
//对日期对象进行格式化
Date date3=new Date();
DateFormat dateformat=DateFormat.getDateInstance(); //获取日期格式对象,具备着默认的风格。
//DateFormat dateformat=DateFormat.getDateInstance(DateFormat.FULL); //这是另一种格式,上面的是默认格式,还有其他的格式。
dateformat=DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);
String str_date=dateformat.format(date3);
System.out.println(str_date);
//自定义格式
dateformat=new SimpleDateFormat("yyyy-MM-dd");
String str_date1=dateformat.format(date3);
System.out.println(str_date1);
/*
* 将日期对象转成日期格式的字符串
* 使用的是DateFormat类中的format方法。比如上面的例子。
* 将日期格式的字符串转成日期对象
* 使用的是DateFormat类中的parse方法。
* */
/*
* 该程序运行结果为:
* 1496796013615
* Wed Jun 07 08:40:13 CST 2017
* Wed Jun 07 08:40:13 CST 2017
* 2017年6月7日 上午08时40分13秒
* 2017-06-07
* */
}
}
package day20;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateTest {
/*
* 练习:"2017-5-17"到"2017-5-20"中间有多少天?
* 思路:
* 两个日期相减就行了,咋减?
* 必须要有两个可以进行减法运算的数才行。就可以用毫秒值。
* 如何获取毫秒值?通过date对象。
* 如何获取date对象?可以将字符串转成date对象。
*
* 1.将日期格式的字符串转成date对象。
* 2.将date对象转成毫秒值。
* 3.相减,变成天数。
*/
public static void main(String[] args) throws ParseException {
String str_Date1="2017--5--17";
String str_Date2="2017--5--20";
test(str_Date1,str_Date2);
}
public static void test(String str_Date1,String str_Date2) throws ParseException {
//1.将日期格式的字符串转成date对象。 定义日期格式对象
//DateFormat dateFormat=DateFormat.getDateInstance(); //这个是默认格式的日期,下面是自定义格式
DateFormat dateFormat=new SimpleDateFormat("yyyy--MM--dd");
Date date1=dateFormat.parse(str_Date1);
System.out.println(date1);
Date date2=dateFormat.parse(str_Date2);
System.out.println(date2);
//2.将date对象转成毫秒值。
long time1=date1.getTime();
long time2=date2.getTime();
//3.相减
long time=Math.abs(time1-time2);
System.out.println(time/1000/60/60/24);
/*
* 该程序运行结果为:
* Wed May 17 00:00:00 CST 2017
* Sat May 20 00:00:00 CST 2017
* 3
* */
}
}
Calendar类:
常用方法:
set(); 获取指定日期
add(); 日期的偏移
get(); 获取当前日期
package day20;
import java.util.Calendar;
public class CalendarDemo {
public static void main(String[] args) {
Calendar c=Calendar.getInstance();
c.set(2017, 5, 20);
c.add(Calendar.YEAR,-1);
showDate(c);
}
private static void showDate(Calendar c) {
int year=c.get(Calendar.YEAR);
int month=c.get(Calendar.MONTH)+1; //注意月份是0-11表示的,0表示1月以此类推。
int day=c.get(Calendar.DAY_OF_MONTH);
int week=c.get(Calendar.DAY_OF_WEEK); //周日是一周的第一天,所以这里要处理一下
System.out.println(year+"年"+month+"月"+day+"日"+" "+getWeek(week));
}
public static String getWeek(int i) {
String[] weeks={"","星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
return weeks[i];
}
}
例:
将C盘的一个文件复制到D盘
package day20;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyTextTest {
private static final int BUFFER_SIZE = 1024*3;
/*
* 需求:将C盘的一个文件复制到D盘
* 分析:读取C盘文件中的数据,并写入到d盘当中。(这就是复制的原理)
*/
public static void main(String[] args) throws IOException {
method_2(); //这个比较常用,效率高,读一大片写一次,循环次数少。
}
public static void method_1() throws IOException {
//1.读取一个已有的文本文件,使用字符读取流和文件相关联。
FileReader fr=new FileReader("demo.txt");
//2.创建一个目的,用于存储读到的数据。
FileWriter fw=new FileWriter("CopyText_1.txt");
//3.频繁的读写操作。
int ch=0;
while((ch=fr.read())!=-1){
fw.write(ch);
}
//4.关闭流资源。
fw.close();
fr.close();
}
public static void method_2() {
FileReader fr=null;
FileWriter fw=null;
try {
fr=new FileReader("demo.txt");
fw=new FileWriter("CopyText_2.txt");
//创建一个临时容器,用于缓存读取到的字符
char[] buf=new char[BUFFER_SIZE];
//定义一个变量记录往数组里装的字符的个数
int len=0;
while((len=fr.read(buf))!=-1){
fw.write(buf, 0,len);
}
} catch (IOException e) {
//System.out.println("读写失败");
throw new RuntimeException();
}
finally{
if(fr!=null)
try {
fr.close();
} catch (IOException e1) {
e1.printStackTrace();
}
if(fw!=null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字符流的缓冲区:
缓冲区的出现提高了对数据的读写效率。上面例子中BUFFER_SIZE就是自定义的缓冲区。
对应类:BufferWriter 特有方法:newline();
BufferReader 特有方法:readline();
缓冲区要结合流才可以使用。
在流的基础上对流的功能就行了增强。
package day20;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
public class BufferedDemo {
public static void main(String[] args) throws IOException {
bufferedWriter();
bufferedReader();
}
public static void bufferedWriter() throws IOException {
FileWriter fw=new FileWriter("demo.txt");
//为了提高写入效率,使用了字符流缓冲区。
//创建一个字符写入流的缓冲区对象,并和指定要被缓冲的流对象相关联。
BufferedWriter bufw=new BufferedWriter(fw);
//使用缓冲区的写入方法将数据写入到缓冲区中。
bufw.write("abcdef");
bufw.newLine();
bufw.write("wqnmlgb");
//使用缓冲区的刷新方法将数据刷新到目的地中。
bufw.flush();
//关闭缓冲区,其实关闭的就是被缓冲的流对象。
bufw.close();
}
public static void bufferedReader() throws IOException {
FileReader fr=new FileReader("demo.txt");
BufferedReader bufr=new BufferedReader(fr);
String line=null;
while((line=bufr.readLine())!=null){
System.out.println(line);
}
bufr.close();
}
}
例:模拟一个BufferedReader.
package day20;
import java.io.FileReader;
import java.io.IOException;
public class MyBufferReader {
/**
* 自定义读取缓存区,其实就是模拟一个BufferedReader.
* 分析:
* 缓冲区中无非就是封装了一个数组,并对外提供了更多的方法对数组进行访问。
* 其实这些方法最终操作的都是数组的角标。
*
* 缓冲的原理:
* 其实就是从源中获取一批数据装进缓冲区中,再从缓冲区中不断地取出一个一个数据。
* 此次取完后,再从源中继续取一批数据装进缓冲区,当源中的数据取完后,用-1作为结束标记。
*/
private FileReader r;
//定义一个数组作为缓冲区
private char[] buf=new char[1024];
//定义一个指针用于操作这个数组中的元素。当操作到最后一个元素后,指针归零。
private int pos=0;
//定义一个计数器用于记录缓冲区中数据个数。当该数据减为0,就从源中继续获取数据到缓冲区。
private int count=0;
MyBufferReader(FileReader r){
this.r=r;
}
public int myRead() throws IOException{
//1.从源中获取一批数据到缓冲区中。需要先做判断,只有计数器为0时,才需要从源中获取数据
if(count==0){
count=r.read(buf);
pos=0; //每次获取数据到缓冲区后,角标归零。
}
if(count<0)
return -1;
char ch=buf[pos++];
count--;
return ch;
/* 这样的代码是初稿,思路很清晰,上面是修改之后的。
if(count==0){
count=r.read(buf);
if(count<0)
return -1;
pos=0; //每次获取数据到缓冲区后,角标归零。
char ch=buf[pos];
pos++;
count--;
return ch;
}else if(count>0){
char ch=buf[pos];
pos++;
count--;
return ch;
}*/
}
public String myReadLine() throws IOException{
StringBuilder sb=new StringBuilder();
int ch=0;
while((ch=myRead())!=-1){
if(ch==‘\r‘)
continue;
if(ch==‘\n‘)
return sb.toString();
//将从缓冲区读到的字符,存储到缓存行数据的缓冲区中。
sb.append((char)ch);
}
if(sb.length()!=0){ //若没有这个if代码块,demo.txt的最后一行如果没有回车符,那么MyRead就会丢失最后一行。
return sb.toString();
}
return null;
}
public void myClose() throws IOException{
r.close();
}
}
package day20;
import java.io.FileReader;
import java.io.IOException;
public class MyBufferReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fr=new FileReader("demo.txt");
MyBufferReader bufr=new MyBufferReader(fr);
String line=null;
while((line=bufr.myReadLine())!=null){
System.out.println(line);
}
bufr.myClose();
}
}
缓冲区中的read方法对FileReader中的Read进行了功能的加强,效率的提高。这就是装饰设计模式。
装饰设计模式:
对一组对象的功能进行增强时,就可以使用该模式就行问题的解决。
装饰和继承都具有一样的特点:进行功能的扩展。那么他们有何区别呢?
假如有一个继承体系:
Writer
|--TextWriter:用于操作文本
|--MediaWriter:用于操作媒体。
想要对操作的动作进行效率的提高,按照面向对象的思想,可以通过继承对具体对象进行功能的扩展。
效率提高需要加入缓冲技术。
Writer
|--TextWriter:用于操作文本
|--BufferTextWriter:加入了缓冲技术的操作文本的对象
|--MediaWriter:用于操作媒体。
|--BufferMediaWriter:加入了缓冲技术的操作媒体的对象
到这里就哦了,但是这样做好像并不理想。
如果这个体系就行功能扩展,又多了流对象。那么这个流要提高效率,是不是也要产生子类呢?对,这时就会发现只为提高功能,进行的继承,导致继承体系越来越臃肿。不够灵活。
重新思考这个问题?
既然加入的都是同一组技术——缓冲。前一种是让缓冲和具体的对象相结合。
可不可以将缓冲进行单独的封装,哪个对象需要缓冲就将那个对象和缓冲关联。
class BufferWriter extends Writer{
Buffer(Writer w){}
}
这时这个体系就变成了这样:
Writer
|--TextWriter:用于操作文本
|--MediaWriter:用于操作媒体
|--BufferWriter:用于提高效率
到这里就可以发现,装饰比继承更加灵活。特点:装饰类和被装饰类都必须所属同一个接口或者父类。
装饰设计模式的一个简单的演示
package day20;
public class PersonDemo {
public static void main(String[] args) {
Person p=new Person();
NewPerson p1=new NewPerson(p);
p1.chifan();
NewPerson2 p2=new NewPerson2();
p2.chifan();
/*
* 那么问题来了,继承也可以解决这种问题,他们有何区别呢?
*/
}
}
class Person{
void chifan(){
System.out.println("吃饭");
}
}
//这个类的出现是为了增强Person而出现的。
class NewPerson{
private Person p;
NewPerson(Person p){
this.p=p;
}
public void chifan(){
System.out.println("开胃酒");
p.chifan();
System.out.println("饭后甜点");
}
}
class NewPerson2 extends Person{
public void chifan(){
System.out.println("开胃酒");
super.chifan();
System.out.println("饭后甜点");
}
}
LineNumberReader 了解即可,可以读行号。
package day20;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
public class LineNumberReaderDemo {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
FileReader fr=new FileReader("demo.txt");
LineNumberReader lnr=new LineNumberReader(fr);
String line=null;
lnr.setLineNumber(100); //默认行号是从1开始的,这个方法可以自己设置从几开始。
while((line=lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
到这里,字符流的基本对象学习完了,接下来学习字节流。
字节流
字节流的基本操作与字符流类相同。
但它不仅可以操作字符,还可以操作其他媒体文件。比如Copy一个jpg文件。
package day22;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
demo_write();
demo_read();
}
public static void demo_read() throws IOException {
/*
available()方法就是返回文件的字节数,用这个可以创建一个可文件长度刚好一样的为数组,读文件就简单多了
但是这个慎用,如果获取一个2G多的电影,累死你。
FileInputStream fis=new FileInputStream("demo2.txt");
byte[] buf=new byte[fis.available()];
fis.read(buf);
System.out.println(new String(buf));
*/
//还是这个最常用。
FileInputStream fis=new FileInputStream("demo2.txt");
byte[] buf=new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
fis.close();
/*
//1.创建一个读取流对象,和指定文件关联。
FileInputStream fis=new FileInputStream("demo2.txt");
//2.读数据。一次读取一个字节。读中文就不行,一个汉字两个字节。
int ch=0;
while((ch=fis.read())!=-1){
System.out.println((char)ch);
}
fis.close();
*/
}
public static void demo_write() throws IOException {
//1.创建字节输出流对象。用于操作文件。
FileOutputStream fos=new FileOutputStream("demo2.txt");
//2.写数据。直接写入到了目的地中。
fos.write("abcdefg".getBytes());
fos.close(); //不需要刷新了,但是关闭还是要的。
}
}
练习:复制一个mp3文件。
package day22;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyMp3Test {
/**
* 需求:复制一个mp3文件。其实和复制txt文件是一样的,就是换了一个对象。
*/
public static void main(String[] args) throws IOException {
copy_1();
copy_2();
}
public static void copy_2() throws IOException {
FileInputStream fis=new FileInputStream("LoginScreenLoop.mp3");
BufferedInputStream bufis=new BufferedInputStream(fis);
FileOutputStream fos=new FileOutputStream("CopyMp3.mp3");
BufferedOutputStream bufos=new BufferedOutputStream(fos);
int ch=0;
while((ch=bufis.read())!=-1){
bufos.write(ch);
}
bufos.close();
bufis.close();
}
public static void copy_1() throws IOException {
FileInputStream fis=new FileInputStream("LoginScreenLoop.mp3");
FileOutputStream fos=new FileOutputStream("CopyMp3.mp3");
byte[] buf=new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1){
fos.write(buf,0,len);
}
fos.close();
fis.close();
}
}
本文出自 “12946849” 博客,请务必保留此出处http://12956849.blog.51cto.com/12946849/1932971
以上是关于javaIO流的主要内容,如果未能解决你的问题,请参考以下文章