_____IO(Input Output)流_____
IO流就是用来处理设备之间的数据传输的,而Java对数据的的操作时通过流的方式,其操作流的类都在IO包中。
按流向分为:输入流,输出流
对于数据而言,文本居多,所以Java单独建立了字符流来处理文本数据
这是字符流抽象基类的两个实现子类,主要是对文本文件进行IO操作。
FileWriter fw = null;
try {
//只能创建文件,不能创建路径,路径必须为已有路径
fw = new FileWriter("D:/demo.txt");
fw.write("黑马");
//此类具有缓存机制,写入的数据暂时保存在流中,需要刷新才能写入到文本文件中
//close()关闭流时会自动执行一次刷新操作,所以此处可以省略
fw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
FileReader fr = null;
try {
fr = new FileReader("D:/demo.txt");
char[] cbuf = new char[1];// 读取到数据在非UTF-8编码下为2KB大小,char为2字节16位;UTF-8编码下为3KB大小
int len = -1;
while ((len = fr.read(cbuf)) != -1) {
System.out.println(len + ":" + new String(cbuf, 0, len));
System.out.println(Integer.toHexString(cbuf[0]));
/*
* Windows系统创建的UTF-8文本文件通常会在文本的开头放置一个标识字节序的编码;
* 所以自己通过windows系统创建的文本可能出现读取出来的第一位长度为1,内容为空,存储的是字节序
* 字节序分为如下两类:
* FFFE:Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端;
* FEFF:Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端;
* 比如 int a = 0x05060708:
* 在BIG-ENDIAN的情况下存放为: 字节号 0 1 2 3 数据 05 06 07 08
* 在LITTLE-ENDIAN的情况下存放为: 字节号 0 1 2 3 数据 08 07 06 05
*/
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null)
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//读取文本文件并且写入另一个文本文件
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("D:/demo.txt");
fw = new FileWriter("D:/demo2.txt");
//无参数的read方法只能读取一个字符,对于硬盘磁针就好来回切换,照成时间的浪费
//所以采用大小合适的一组数据
char[] cbuf = new char[1024];
int len = -1;
while((len = fr.read(cbuf)) != -1) {
fw.write(cbuf, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally { //打开的流必须关闭,不然会占用较多资源
if(fr != null)
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
if(fw != null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
针对文本IO流Java提供的字符流缓存装饰类,通过缓冲各个字符来得到更加高效的读写操作。
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader("D:/demo.txt"));
bw = new BufferedWriter(new FileWriter("D:/demo2.txt"));
//BufferedReader可以直接读取文本中的一行
String line = null;
while((line = br.readLine()) != null) {
//BufferedWriter可以直接将字符串写入到文本中
bw.write(line);
//不用写入换行符,可根据不同系统来自动换行
bw.newLine();
//Buffered能显著提高字符读写的效率,不过需要刷新,不然缓存在内存中不会写入硬盘
bw.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br != null)
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
if(bw != null)
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
这专门针对文本文件行读取的类,他提供了行读取,获取行号,标记等方法。
LineNumberReader lnr = null;
try {
lnr = new LineNumberReader(new FileReader("./src/com/itheima/Demo.java"));
System.out.println(lnr.getLineNumber());
String line = null;
//int i=0;
while((line = lnr.readLine()) != null) {
System.out.println(lnr.getLineNumber()+": "+line);
/*
if(++i==10) {
lnr.mark(2);//在lnr流中标记一个位置,参数为超过多少字符后,标记“可能”会失效,失效时调用reset会抛出IOException异常
lnr.skip(12);//跳过多少字符,windows中回车符‘\r\n’算两个,会影响到下一行
}
if(i== 20) {
lnr.setLineNumber(0);
lnr.reset();//让流从新定义到mark所标记的位置。lineNumber也回到mark标记时的状态,可以在“后面”设置setLineNumber,在前面设置会被reset内部覆盖
lnr.setLineNumber(0);
}
*/
}
} catch (FileNotFoundException e) {
throw new RuntimeException("文件路径不存在失败");
} catch (IOException e) {
throw new RuntimeException("读取失败");
} finally {
if(lnr != null)
try {
lnr.close();
} catch(IOException e) {
throw new RuntimeException("关闭流失败");
}
}
通用的字节流,计算机中的所有数据都是按照字节的方法存储的,对于文件,音频,视频,图片等等所有数据都可以用字节流进行读写操作。
这是字节流抽象基类的两个实现子类,主要是对文件进行IO操作。
public static void fileInputStreamDemo() throws IOException {
FileInputStream fis = new FileInputStream("D:/demo.txt");
/*
* 1.一次读取完整个文件
* 这种方式只适合较小的数据,JVM的默认内存为64M,读一个大小为1G的视频文件,则内存装不下。
*/
//获取还没读取文件数据的长度
int len = fis.available();
//创建于文本数据长度相同的数组
byte[] buf = new byte[len];
//读取整个文件
fis.read(buf);
fis.close();
//因为是文本文件,所以我们可以打印到控制台
System.out.println(new String(buf));
/*
* 2.每次读取固定长度数据
* 这种方式是最通用的
*/
fis = new FileInputStream("D:/demo.txt");
//创建大小为1KB的缓存数组
buf = new byte[1024];
while((len = fis.read(buf)) != -1) {
System.out.println(new String(buf, 0, len));
}
fis.close();
}
public static void fileOutputStreamDemo() throws IOException {
FileOutputStream fos = new FileOutputStream("D:/demo2.txt");
fos.write("中关村黑马".getBytes());
fos.close();
}
public static void fileStreamDemo() throws IOException {
FileInputStream fis = new FileInputStream("D:/demo.txt");
FileOutputStream fos = new FileOutputStream("D:demo2.txt");
byte[] buf = new byte[1024];
int len = -1;
while((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fis.close();
fos.close();
}
与字符流一样拥有缓存装饰类,通过缓冲各个字符来得到更加高效的读写操作。
public static void bufferedStreamDemo() throws IOException {
//我们来拷贝一份Mp3文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/song.mp3"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:song2.mp3"));
byte[] buf = new byte[1024<<3];
int len = -1;
while((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
}
bis.close();
bos.close();
}
/**
* 使用read()无参数方法读取单字节的时候返回类型为int,还涉及到一个原理,
* 在Byte转Int的时候,有两种情况。
* 1.保持值不变:int i = (int)b;
* 2.保持最低字节不变:int i = b & 0xff;
* 由于read读取的是byte而返回的是int类型,如果中间有“1111-1111”字节存在
* 采用1:则返回同样的值“-1”也就是“11111111-11111111-11111111-11111111”
* 采用2:则返回“255”也就是“00000000-00000000-00000000-11111111”
* 抵用中方法会让值和字节流结束标识混淆,所以在read中采用的是2保持最低字节不变,其他补0
*/
在以上的操作中,我们每次都手写了文件的绝对路径,当一个文件使用多次时,维护起来就会巨困难,而且文件也是一类对象,于是File类出现了。
public class Demo {
public static void main(String[] args) {
//直接使用String绝对路径创建
File file1 = new File("D:"+File.separator+"direc");
//两段路径连接,中间可以没有分离符
File file2 = new File("D:", "demo.txt");
//也是两段连接,但是前者使用的是File对象
File file3 = new File(file1, "kk/demo.txt");
System.out.println("file1:"+file1);
System.out.println("file2:"+file2);
System.out.println("file3:"+file3);
System.out.println("路径:"+file3.getPath());
System.out.println("绝对路径:"+file3.getAbsolutePath());
System.out.println("文件或者文件夹名称:"+file3.getName());
System.out.println("父目录的路径名字:"+file3.getParent());
System.out.println("文件最后一次修改时间:"+file3.lastModified());
System.out.println("文件大小:"+file3.length());
}
}
/*
结果:
file1:D:\direc
file2:D:\demo.txt
file3:D:\direc\kk\demo.txt
路径:D:\direc\kk\demo.txt
绝对路径:D:\direc\kk\demo.txt
文件或者文件夹名称:demo.txt
父目录的路径名字:D:\direc\kk
文件最后一次修改时间:1453352590015
文件大小:7
*/
//renameTo重命名方法内部就是拷贝和删除源文件,所以可以用来转移文件的位置
public static void main(String[] args) {
//fi为需要重命名的文件,fo为重命名后的文件
File fi = new File("D:"+File.separator+"demo.txt");
File fo = new File("E:"+File.separator+"kk","demotwo.txt");
//若源文件不存在,操作就没必要继续
if(fi.exists()) {
//先判断目标文件的父目录是否存在,不存在则创建
if(!fo.getParentFile().exists())
fo.getParentFile().mkdirs();
//重命名并且转存文件,成功返回true
System.out.println(fi.renameTo(fo));
}
}
在Java中配置文档一般使用properties和xml两种:properties读取的文档其实就是key=value的形式
/**
* 首位为#是注释信息不会被加载,其余必须为键值对格式
* @throws IOException
*/
public static void loadDemo() throws IOException {
Properties p = new Properties();
File f = new File("E:/editInfo.ini");
FileInputStream fis = new FileInputStream(f);
//从输入流中读取键值对
p.load(fis);
//将属性列表输出到指定的输出流,这里是输出到控制台
p.list(System.out);
FileOutputStream fos = new FileOutputStream(f);
//添加键值对
p.setProperty("encoding", "GBK");
//将此 Properties 表中的属性列表(键和元素对)写入输出字符。
p.store(fos, "Encoding");
fis.close();
fos.close();
}
向文本输出流打印对象的格式化表示形式,也就是格式化输出
/**
* 获取键盘输入
*/
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out, true);
//PrintWriter out = new PrintWriter("test.txt");
//PrintWriter out = new PrintWriter(new FileWriter("test.txt"), true);
//PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("test.txt")), true);
String line = null;
while( (line=bufr.readLine()) != null) {
//out.write(line.toUpperCase());
out.println(line);
//out.flush();
}
out.close();
bufr.close();
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
//拆分方法
public static void splitFile() throws IOException {
//需要被拆分的源文件
File source = new File("song.mp3");
//拆分后文件的存储路径
File toDir = new File("splitdir");
toDir.mkdirs();
BufferedInputStream bfis = new BufferedInputStream(new FileInputStream(source));
BufferedOutputStream bfos;
//定义拆分文件的大小为64KB
byte[] bbuf = new byte[1024<<6];
int len = 0;
int count = 0;
while((len=bfis.read(bbuf)) != -1) {
//拆分后文件的存储格式为源文件名+序号.part
bfos = new BufferedOutputStream(new FileOutputStream(toDir.getAbsolutePath()+File.separator+"song"+count+++".part"));
bfos.write(bbuf, 0, len);
bfos.close();
}
bfis.close();
}
//合并方法
public static void sequenceFile() throws IOException {
//合并后文件的存储位置
File toDir = new File("sequencedir");
//需要合并的文件位置
File sourceDir = new File("splitdir");
toDir.mkdirs();
//筛选出符合要求的合并文件
File[] arr = sourceDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return name.startsWith("song") && name.endsWith(".part");
}
});
//对需要合并的文件进行排序
Arrays.sort(arr, new Comparator() {
@Override
public int compare(File arg0, File arg1) {
return arg0.getName().compareTo(arg1.getName());
}
});
BufferedInputStream bfis;
BufferedOutputStream bfos = new BufferedOutputStream(new FileOutputStream(new File(toDir,"song.mp3")));
for(File f:arr) {
bfis = new BufferedInputStream(new FileInputStream(f));
byte[] bbuf = new byte[1024*2];
int len = -1;
while((len = bfis.read(bbuf)) != -1)
bfos.write(bbuf, o, len);
bfis.close();
}
bfos.close();
}
被操作对象必须实现Serializable接口,此流可以将对象持久化到硬盘上。
//将对象持久化到硬盘
public static void writeObj() throws IOException {
File fto = new File("person.object");
if(!fto.exists()) fto.createNewFile();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fto, true));
oos.writeObject(new Person("lis3", "男", 20));
oos.close();
}
//将硬盘上的对象加载到内存中
public static void readObj() throws FileNotFoundException, IOException, ClassNotFoundException {
File fto = new File("person.object");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fto));
Person p = null;
while((p = (Person) ois.readObject()) !=null) {
System.out.println(p);
}
ois.close();
}
实现Serializable接口类中会出现序列化标记
* 给类一个序列化标记,不显示申明则依据类中属性算:访问修饰符不同都不能读取
*
* static 和transient修饰的不能被持久化
*/
private static final long serialVersionUID = 1L;
这个标记可以由用户显示赋值,也可以由Java根据类的字段和属性等等计算出来,主要是标记是否为同一个类
管道流就是将输入和输出绑定到一根管道上,有输入才有输出,没输入则输出等待。
//创建一个输入的多线程对象
class Read implements Runnable {
private PipedInputStream in;
public Read(PipedInputStream in) {
super();
this.in = in;
}
@Override
public void run() {
//与常规无异的读取操作
try {
byte[] buf = new byte[1024];
System.out.println("Read:等待数据写入");
int len = in.read(buf);
System.out.println("Read:数据读取成功");
String s = new String(buf, 0, len);
System.out.println(s);
in.close();
} catch (IOException e) {}
}
}
//创建一个输出的多线程对象
class Write implements Runnable {
private PipedOutputStream out;
public Write(PipedOutputStream out) {
super();
this.out = out;
}
@Override
public void run() {
//与常规无异的写入操作
try {
System.out.println("Write:准备写入数据,等待6秒");
Thread.sleep(6000);
out.write("大眼猫".getBytes());
System.out.println("Write:数据写入成功");
out.close();
} catch (Exception e) {}
}
}
public class Demo {
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(w).start();
new Thread(r).start();
}
}
此类封装了读和写的方法;它内部有个大型的byte类型数组,能对数据在任意点开始读取和修改:用它就可以实现多线程下载。