Java基础——IO流(下)之字节流

时间:2020-12-04 20:19:54


个人小结:数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。根据处理数据类型的不同可分为:字符流和字节流。本篇主要介绍IO流中的字节流。

一、字节流

1、如果要操作除文本数据类型之外的数据,比如图片,就要用到 字节流。

2、读写字节流:InputStream   读取(输入)流, OutputStream  写入(输出)流
<span style="font-family:Microsoft YaHei;font-size:12px;">/*
需求:
复制一个图片
思路:
1、用字节读取流对象和图片关联
2、用字节写入流对象创建一个图片文件,用于存储获取到的图片数据
3、通过循环读写,完成数据存储
4、关闭资源

注意:不要用字符流来处理除文本以外的文件,容易出问题!
*/
import java.io.*;
class CopyPic
{
public static void main(String[] args)
{
FileInputStream fis= null;
FileOutputStream fos= null;
try
{
//读取流关联要复制的文件
fis = new FileInputStream("D:\\JAVA学习\\线程运行状态.jpg");
//写入流关联指定复制的文件名
fos = new FileOutputStream("c:\\1.jpg");
//定义一个缓冲数组
byte[] buf = new byte[1024];
int len = 0;
//边读边写
while ((len = fis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException("复制文件失败!");
}
finally
{
try
{
if(fis!=null)
fis.close();//关流
}
catch (IOException e)
{
throw new RuntimeException("读取关闭失败");
}
try
{
if(fis!=null)
fos.close();//关流
}
catch (IOException e)
{
throw new RuntimeException("写入关闭失败");
}
}
}
}</span>

二、字节流缓冲区

BufferedOutputStreamBufferedInputStream 1、作用:提高了字节流的读写效率。 2、特点: read():会将字节byte型值提升为int型值
        write():会将int型强转为byte型,即保留二进制数的最后八位。
3、原理:先将数据读取一部分,循环,直到数据全部读取完毕。        
4、自定义读取字节流缓冲区
        需求:根据字节流缓冲区的原理,自定义一个字节流缓冲区。
注意:
        1、字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。
        因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1。那么就会出现数据还没有读完就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。
       所以,为了避免这种情况将读到的字节进行int类型的提升。并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。而在写入数据时,只写该int类型数据的最低8位。
        2、byte类型的-1提升为int类型时还是-1。原因:因为在二进制位中的8个1前面补的全是1导致的。如果在8个1前面补0,既可以保留原字节数据不变,又可以避免-1的出现。这时只用将byte型数据 &255 即可。
示例代码:演示自定义缓冲区,并用来拷贝MP3文件。
<span style="font-family:Microsoft YaHei;font-size:12px;">import java.io.*;
class MyBufferedInputStream
{
private InputStream in;
private byte[] buf = new byte[1024*4];
private int pos = 0,count=0;
MyBufferedInputStream(InputStream in)
{
this.in = in;
}
//一次读一个字节,从缓冲区(字符数组)获取
public int myRead() throws IOException
{
//通过in对象读取硬盘上的数据,存储到buf中。
if (count==0)
{
count = in.read(buf);

if (count<0)//文件数据全部被读取出来了
return -1;

pos = 0;//初始化指针
byte b = buf[pos];
count--;//每读一个字节,数组中的字节数就少一个
pos++;
return b&255;//返回的byte类型提升为int类型
}
else if (count>0)//如果数组中数据还没被读完,就继续读
{
byte b = buf[pos];
count--;
pos++;
return b&0xff;//返回的byte类型提升为int类型
}
return -1;
}
//定义关闭资源动作
public void myClose() throws IOException
{
in.close();
}
}</span>
//通过自定义缓冲区,演示MP3的复制。
import java.io.*;
class CopyMp3
{
public static void main(String[] args) throws IOException
{
long start = System.currentTimeMillis();
copy_2();
long end = System.currentTimeMillis();
System.out.println((end-start)+"毫秒");//查看复制所需时间
}
//通过自定义缓冲区演示
public static void copy_2() throws IOException
{
//创建缓冲区,并关联复制对象文件
MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\0.mp3"));
//创建缓冲区,关联目标文件
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3"));

int by = 0 ;

System.out.println("第一个字节:"+bufis.myRead());
//不停地读取写入
while ((by=bufis.myRead())!=-1)
{
bufos.write(by);
}
bufos.close();//关资源
bufis.myClose();//关资源
}
}

三、读取键盘录入
System.out: 标准输出设备:控制台
System.in : 标准输入设备:键盘
示例: 需求:通过键盘录入数据,当录入一行数据后,就将该行数据转成大写并进行打印,如果录入的数据是over,那么停止录入。
代码:
<span style="font-family:Microsoft YaHei;font-size:12px;">import java.io.*;
class ReadIn
{
public static void main(String[] args) throws IOException
{
//获取键盘录入对象。
InputStream in = System.in;
//创建一个容器存储键盘录入的数据
StringBuilder sb = new StringBuilder();

while (true)
{
int ch= in.read();

if(ch=='\r')
continue;
if(ch=='\n')
{
String s = sb.toString();
if("over".equals(s))//如果录入的数据是over,那么停止录入
break;
System.out.println(s.toUpperCase());//将读取的字符串转成大写后打印
sb.delete(0,sb.length());
}
else
sb.append((char)ch);
}
}
}</span>
运行结果:
Java基础——IO流(下)之字节流
四、读取转换流
通过刚才的键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理,即 readLine()方法。 那么能不能直接使用readLine方法来完成键盘录入的一行数据的读取呢?
不能!由于readLine方法是BufferedReader类中的方法,而键盘录入的read方法是字节流InputStream的方法。
那么能不能将字节流转成字符流再使用字符流缓冲区的readLine方法呢?可以!用转换流 InputStreamReader!
那么上例代码可改为:
<span style="font-family:Microsoft YaHei;font-size:12px;">import java.io.*;
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
//获取键盘录入对象。
//InputStream in = System.in;

//将字节流对象转成字符流对象,使用转换流,InputStreamReader
//InputStreamReader isr = new InputStreamReader(in);

//为了提高效率,将字符流进行缓冲区技术的高效操作,使用BufferedReader

//BufferedReader bufr = new BufferedReader(isr);

//键盘录入最常见写法!强记!
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));

/*
OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bufw = new BufferedWriter(osw);
*/
BufferedWriter bufw =
new BufferedWriter(new OutputStreamWriter(System.out));

String line = null;
while ((line=bufr.readLine())!=null)
{
if ("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}</span>

五、IO流操作规律(最重要部分)

1、需求:将键盘输入数据打印出来
源:键盘录入
目的:控制台
2、需求:想把键盘录入的数据存储到一个文件中
源:键盘
目的:文件
3、需求:想要将一个文件的数据打印在控制台上。
源:文件
目的:控制台
最痛苦的就是流对象有很多,不知道该用哪个?

通过三个明确来完成:
1、明确源和目的。
源:输入流。InputStream  、Reader
目的:输出流。OutputStream  、Writer
2、操作的数据是否是纯文本?
是:用字符流
不是:用字节流
3、当体系明确后,再明确要使用哪个具体对象,
通过设备来进行区分:
源设备:内存,硬盘,键盘
目的设备:内存,硬盘,控制台

应用规律实例分析: 需求1:将一个文本文件中的数据存储到另一个文件中,复制文件
1、源:因为是源,所以使用读取流:InputStream  或 Reader
是不是操作文本文件?是!这时就可以选择 Reader
2、接下来明确要使用该体系中的哪个对象?
明确设备:硬盘上的一个文件
Reader体系中可以操作文件的对象是FileReader
3、是否要提高效率?
是!加入Reader体系中的缓冲区: BufferedReader。
4、写出代码:
FileReader fr = new FileReader("a.txt");
BufferedReader bufr  = new BufferedReader(fr);
5、目的:是目的就要用 OutputStream 或者Writer
是不是纯文本?  是!用Writer!
6、明确设备:硬盘的一个文件
Writer 体系中可以操作文件的对象是FileWriter
7、是否需要提高效率?
是! 加入Writer体系中的缓冲区: BufferedWriter
8、写出代码:
FileWriter fw = new FileWriter("b.txt");
BufferedWriter bufw = new BufferedWriter(fw);

练习:将一个图片文件中的数据存储到另一个文件中,复制文件,要按照以上格式自己完成三个明确。
1、源:硬盘中的一个文件
是不是操作文本文件?--不是!所以用 InputStream.
明确使用该体系中哪个对象?--设备是硬盘,所以用 FileInputStream
是否要提高效率?--要!加入该体系中的缓冲区 BufferedInputStream
2、目的:硬盘
是不是操作文本?--不是,所以用 OutputStream
用哪个对象?--设备是硬盘,FileOutputStream
是否要提高效率?--要!加入该体系中的缓冲区 BufferedOutputStream

需求2:将键盘录入的数据保存到一个文件中。
1、源:InputStream 或  Reader
是不是纯文本?是!用 Reader
设备:键盘。对应的对象是 System.in.
不是选择Reader吗? System.in 不是字节流吗?
为了操作键盘的文本数据方便,转成字符流 按照字符串来操作最方便。
所以,既然明确了Reader,那么就将System.in转成字符流Reader,
用到了 Reader体系中的转换流,即 InputStreamReader.
InputstreamReader  isr = new InputStreamReader(System.in);
要提高效率吗?要! 用 BufferedReader
代码:BufferedReader bufr = new BufferedReader(isr);
2、目的:OutputStream 或  Writer
是否是纯文本?是! 用 Writer
设备:硬盘,一个文件,使用FileWriter
需要提高效率吗?需要!用BufferedWriter
代码:FileWriter fw = new FileWriter("c.txt");
  BufferedWriter bufw =new BufferedWriter(fw);

扩展一下,想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中。
目的:OutputStream  或 Writer
是否是纯文本?是! 用 Writer
设备:硬盘,一个文件,使用FileWriter,但是 FileWriter是使用默认编码表:GBK。
因为存储时需要加入指定的编码表UTF-8,而指定的编码表只有转换流才可以指定。所以要使用的对象是OutputStreamWriter
而该转换流对象要接收一个字节输出流,而且还可以操作文件的字节输出流。
所以代码:OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
需要高效吗?要!BufferedWriter bufw = new BufferedWriter(osw);
所以,记住!转换流什么时候用呢?
转换流是字符和字节之间的桥梁,通常涉及到字符编码转换时需要用到。

练习:将一个文本数据打印在控制台上。按照以上格式完成三个明确。
1、源:文本,所以   Reader
设备:硬盘,一个文件,所以  FileReader
效率:要!所以BufferedReader
2、目的:文本,so  Writer
设备:控制台,so   System.out
但System.out是字节流,所以需要先转换成Writer,这就要用到Writer体系中的转换流:OutputStreamWriter
代码:OutputStreamWriter osw = new OutputStreamWriter(System.out);
要提高效率:BufferedWriter = new BufferedWriter(osw);
示例代码:
<span style="font-family:Microsoft YaHei;font-size:12px;">import java.io.*;
class TransStreamDemo2
{
public static void main(String[] args) throws IOException
{
System.setIn(new FileInputStream("PersonDemo.java"));//设置源
System.setOut(new PrintStream("zzz.txt"));//设置目的

BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));//需求2
//new BufferedReader(new InputStreamReader(new FileInputStream("CopyPic.java")));//需求3
//new BufferedReader(new FileReader("out0.txt"));

BufferedWriter bufw =
//new BufferedWriter(new OutputStreamWriter(new FileOutputStream("out0.txt")));//需求2
new BufferedWriter(new OutputStreamWriter(System.out));//需求2

String line = null;
while ((line=bufr.readLine())!=null)
{
if ("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}</span>

六、保存异常的日志信息
        正常开发中,程序在执行的时候出现的问题,需要用文件存储起来,这样方便查看和调整。
示例:

<span style="font-family:Microsoft YaHei;font-size:12px;">import java.io.*;
import java.util.*;
import java.text.*;
class ExceptionInfo
{
public static void main(String[] args) throws IOException
{
try
{
int[] arr = new int[2];
System.out.println(arr[3]);
}
catch (Exception e)
{
try
{
Date d = new Date();//创建日期对象
//将日期格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(d);
//打印流对象
PrintStream ps = new PrintStream("EXCEPTIONINFO.log");
ps.println(s);//打印时间
System.setOut(ps);//设置输出
}
catch (IOException ex)
{
throw new RuntimeException("日志文件创建失败!");
}
//将异常信息输出到指定输出流
e.printStackTrace(System.out);
}
}
}</span>

七、将系统属性信息保存到指定文本中 

示例代码:
<span style="font-family:Microsoft YaHei;font-size:12px;">import java.util.*;
import java.io.*;
class SystemInfo
{
public static void main(String[] args) throws IOException
{
Properties prop = System.getProperties();//获取系统信息

//System.out.println(prop);
//prop.list(System.out);
prop.list(new PrintStream("sysinfo.txt"));
}
}</span>

八、File类
1、它是文件和目录路径名的抽象表现形式
        2、用来将文件或文件夹封装成对象
        3、方便对文件与文件夹的属性信息进行操作
4File对象可以作为参数传递给流的构造函数
        4、File类的实例是不可变的;即File 对象一旦创建,其表示的抽象路径名将永不改变

九、File类的常见方法
1、创建
blooean createNewFile(); 在指定位置创建文件,返回true,如果该文件已存在,则不创建,并返回false.
和输出流不一样,输出流对象一创建文件,如果该文件已存在,则会直接覆盖。
boolean mkdir():创建文件夹
boolean mkdirs():创建多级文件夹
2、删除
boolean delete();//删除文件或目录。文件存在,返回true;文件不存在或者正在被执行,返回false。
void deleteOnExit();//当程序结束时才删掉文件。
3、判断
boolean  canExecute();//是否是可执行文件
boolean  exists()://文件是否存在,用的较频繁。
isFile();//是否是文件
isDirectory();//是否是文件夹
isHidden(); //是否是隐藏文件
isAbsolute();//文件是否是绝对路径
4、获取信息
getName();//获取文件名
getPath(); //获取文件的相对路径(即创建的对象传入的参数是什么就获取到什么)
getParent();//获取文件父目录。返回的是绝对路径中的父目录。如果是相对路径,则返回null。如果相对路径中有上一层目录,那么该目录就是返回结果。
getAbsolutePath();//获取文件的绝对路径
long lastModified();//返回文件最后一次被修改的时间
long length();//返回文件长度

代码示例:
import java.io.*;
class FileDemo
{
public static void main(String[] args) throws IOException
{
//conMethod();
//method_1();
//method_2();
//method_3();
//method_4();

method_5();
}

public static void method_5()
{
File f1 = new File("c:\\yuanwenjian.txt");
File f2 = new File("c:\\hahha.java");

sop("rename:"+f1.renameTo(f2));
}

public static void method_4()
{
File f = new File("c:\\abc\\a.txt");

sop("path:"+f.getPath());
sop("abspath:"+f.getAbsolutePath());
sop("parent:"+f.getParent());//该方法返回的是绝对路径中的父目录。
//如果获取的是相对路径,返回空null.
//如果相对路径中有上一层目录,那么该目录就是返回结果.
}

public static void method_3() throws IOException
{
File f = new File("D:\\java0913\\day20\\kkk\\file1.txt");

//f.createNewFile();

f.mkdir();

//记住:在判断文件是否是文件或者目录时,必须要先判断该文件
//对象封装的内容是否存在,通过exists判断。
sop("dir:"+f.isDirectory());

sop("file:"+f.isFile());

sop(f.isAbsolute());
}

public static void method_2()
{
File f = new File("file.txt");

//sop("execute:"+f.canExecute());

//sop("exists:"+f.exists());

//创建文件夹
File dir = new File("abc");

sop("mkdir:"+dir.mkdir());

//创建多级文件夹
File dirs = new File("D:\\java0913\\day20\\kk\\hh\\yy\\fdfdfd");

sop("mkdirs:"+dirs.mkdirs());

}

public static void method_1() throws IOException
{
File f = new File("file.txt");
sop("create:"+f.createNewFile());
}
//创建File对象
public static void conMethod()
{
//将a.txt封装成File对象。可以将已有的和未出现的文件或者文件夹封装成对象。
File f1 = new File("a.txt");

//目录和文件分开
File f2 = new File("c:\\abc","b.txt");

File d = new File("c:\\abc");
File f3 = new File(d,"c.txt");

sop("f1:"+f1);
sop("f2:"+f2);
sop("f3:"+f3);

File f4 = new File("c:"+File.separator+"abc"+File.separator+"zzz"+File.separator+"a.txt");

sop("f4:"+f4);
}

public static void sop(Object obj)
{
System.out.println(obj);
}
}

5、列出文件及文件过滤
   
static File[ ] listRoots();//列出可用的文件系统根目录,即系统盘符
    String[ ] list(); //列出当前目录下所有文件,包括隐藏。调用list方法的file对象必须是封装了一个目录。该目录还必须存在。
    String[ ]list(FilenameFilter filter);//返回一个字符串数组,获取目录中满足指定过滤器的文件或目录。
    FilenameFilter:文件名过滤器,是一个接口,其中包含一个方法,accept(Filedir,String name),返回的是boolean型,对不符合条件的文件过滤掉。
    File[ ] listFiles();//返回一个抽象路径名数组,获取当前文件夹下的所有文件和文件夹
    File[ ] ListFiles(FilenameFilterfilter);//返回抽象路径名数组,获取目录中满足指定过滤器的文件或目录。
代码示例:
import java.io.*;
class FileDemo2
{
public static void main(String[] args)
{
//listRootsDemo();
//listDemo();

File dir = new File("c:\\");

File[] files = dir.listFiles();//listFiles返回的是所指定目录下的文件及文件夹的对象所包装成的数组。

for (File f : files )
{
System.out.println(f.getName()+"::"+f.length());
}

}

public static void listDemo2()
{

File dir = new File("d:\\java0913\\day18");

String[] arr = dir.list(new FilenameFilter()//list返回的是所指定目录下的文件及文件夹的名称所包装成的数组。
{
public boolean accept(File dir,String name)
{
//System.out.println("dir:"+dir+"....name::"+name);
return name.endsWith(".java");
}
});

for (String name : arr )
{
System.out.println(name);
}
}

public static void listDemo()
{
File f = new File("c:\\abc.txt");

String[] names = f.list();//调用list方法的file对象必须是封装了一个目录,该目录还必须存在。
for (String name : names )
{
System.out.println(name);
}
System.out.println(names.length);//此时因为abc.txt文件夹存在,所以返回来一个长度为0的数组。

}

public static void listRootsDemo()
{
File[] files = File.listRoots();

for (File f :files)
{
System.out.println(f);
}
}
}

十、递归
需求:列出指定目录下文件或者文件夹,包含子目录中的内容也就是列出指定目录下所有内容。 分析:因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可,在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身,这种编程方式称为:递归!  注意:
1、限定条件,是来结束循环调用,否则是死循环。
2、要注意递归的次数,尽量避免内存溢出。因为每次调用自身的时候都会先执行下一次调用自己的方法,所以会不断在栈内存中开辟新空间,次数过多,会导致内存溢出。

代码示例:
import java.io.*;
class FileDemo3
{
public static void main(String[] args)
{
//关联指定路径
File dir = new File("d:\\java0913");
showDir(dir,0);//列出指定路径下的所有.java文件
//toBin(6);
//long n = getSum(1000);//内存溢出
//System.out.println("n="+n);
}
//带层次的列表
public static String getLevel(int level)
{
StringBuilder sb = new StringBuilder();
sb.append("|--");
//每多一级目录,就多输出指定字符
for (int x= 0;x<level ;x++ )
{
sb.insert(0," ");
}
return sb.toString();
}
//列出指定目录下的所以内容
public static void showDir(File dir,int level)
{
//有层次的输出
System.out.println(getLevel(level)+dir.getName());
level++;
File[] files = dir.listFiles();//获取本目录下的所有文件和目录的抽象路径
for (int x = 0;x<files.length ; x++)
{
if(files[x].isDirectory())//如果目录下还是目录,则继续调用本函数
showDir(files[x],level);
else//否则列出文件
System.out.println(getLevel(level)+files[x]);
}
}
//用递归求和
public static long getSum(long n)
{
if(n==1)
return 1;
return n+getSum(n-1);
}
//用递归转二进制
public static void toBin(int num)
{
if (num>0)
{
toBin(num/2);
System.out.println(num%2);
}
}
}
运行结果:
Java基础——IO流(下)之字节流

练习:删除一个带内容的目录
/*
删除一个带内容的目录
删除原理:
在windows中,删除目录是从里面往外删。
既然是从里往外删除,那就需要用到递归。
*/
import java.io.*;
class RemoveDir
{
public static void main(String[] args)
{
File dir = new File("testDir");//指定目录
removeDir(dir);
}
//删除目录封装成方法
public static void removeDir(File dir)
{
//列出目录下的所有文件和文件夹
File[] files = dir.listFiles();

for (int x= 0; x<files.length;x++ )
{
if(files[x].isDirectory())
removeDir(files[x]);//如果还是目录,继续调用本函数
else
System.out.println(files[x].toString()+"::"+files[x].delete());//删除文件
}
System.out.println(dir+"::dir::"+dir.delete());//删除目录
}
}
练习:将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。建立一个java文件列表文件。
/*
练习:
将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。
建立一个java文件列表文件。

思路:
1.对指定的目录进行递归。
2、获取递归过程中所有的java文件的路径
3、将这些路径存储到集合中。
4、将集合中的数据写入到一个文件中。
*/
import java.io.*;
import java.util.*;
class JavaFileList
{
public static void main(String[] args)
{
File dir = new File("d:\\java0913"); //指定目录

List<File> list = new ArrayList<File>();//定义一个List集合,用于存储.java文件的File对象

fileToList(dir,list);//调用获取文件路径方法
System.out.println(list.size());//打印集合大小

File file = new File(dir,"javaListFile.txt");//指定存入目的文件
writeToFile(list,file.toString());//调用写入文件方法
}
//获取指定文件夹内的所有java文件的绝对路径,并存入集合中的方法
public static void fileToList(File dir,List<File> list)
{
File[] files = dir.listFiles();//列出dir路径下的所以文件和目录

for (File file : files )
{
if (file.isDirectory())
fileToList(file,list);
else //将是.java文件的绝对路径存入
if(file.getName().endsWith(".java"))
list.add(file);
}
}
//将集合中元素写入到一个文本文件中的方法
public static void writeToFile(List<File> list,String javaList)
{
BufferedWriter bufw = null;
try
{
//使用字符流缓冲区对象关联目的文件
bufw = new BufferedWriter(new FileWriter(javaList));
for (File f : list)
{
String path = f.getAbsolutePath();//获取绝对路径
bufw.write(path);//写入路径
bufw.newLine();//换行
bufw.flush();//刷新
}
}
catch (IOException e)
{
throw new RuntimeException("创建列表文件失败");
}
finally
{
try
{
if(bufw!=null)
bufw.close();//关资源
}
catch (IOException e)
{
throw new RuntimeException("关闭资源失败");
}
}
}
}
运行结果:
Java基础——IO流(下)之字节流

十一、Properties类
Properties 是hashtable的子类。也就是说他具备map集合的特点,而且他里面存储的键值对都是字符串。它是集合中和IO技术相结合的集合容器。
特点:可以用于键值对形式的配置文件。
那么在加载数据时,需要数据有固定格式:键=值。
示例代码:
import java.io.*;
import java.util.*;
class PropertiesDemo
{
public static void main(String[] args) throws IOException
{
//setAndGet();
//method_1();
loadDemo();
}

public static void loadDemo() throws IOException
{
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("info.txt");

//将流中的数据加载进集合。用load方法
prop.load(fis);

prop.setProperty("wangwu","85");

FileOutputStream fos = new FileOutputStream("info.txt");
prop.store(fos,"haha");
//System.out.println(prop);
prop.list(System.out);
fos.close();
fis.close();
}

//演示:如何将流中的数据存储到集合中。
//想要将info.txt中键值数据存到集合中进行操作。
/*
思路:
1、用一个流和info.txt文件关联。
2、读取一行数据,将该行数据用"="切割
3、等号左边作为键,右边作为值,存入到Properties集合中即可。
*/

public static void method_1() throws IOException
{
//使用字符读取缓冲流关联文件
BufferedReader bufw = new BufferedReader(new FileReader("info.txt"));

String line = null;
//定义Properties集合
Properties prop = new Properties();
while((line=bufw.readLine())!=null)
{
String[] arr = line.split("=");//切割
//System.out.println(arr[0]+"......."+arr[1]);
prop.setProperty(arr[0],arr[1]);//将等号左边存为键,将等号右边存为值
}
bufw.close();//关流
System.out.println(prop);
}
//设置和获取元素。
public static void setAndGet()
{
Properties prop = new Properties();

prop.setProperty("zhangsan","30");
prop.setProperty("lisi","39");

System.out.println(prop);
String value = prop.getProperty("lisi");
System.out.println(value);

prop.setProperty("lisi",89+"");

Set<String> names = prop.stringPropertyNames();//返回此属性列表中的键集,其中该键及其对应值是字符串.
for (String s : names )
{
System.out.println(s+"::"+prop.getProperty(s));
}
}
}
练习:用于记录应用程序运行次数。如果使用次数已到,那么给出注册提示。
/*
练习:用于记录应用程序运行次数。如果使用次数已到,那么给出注册提示。

很容易想到的是:计数器
可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行了自增,
可是随着该应用程序的退出,该计数器也在内存中消失了,

下一次再启动该程序,又重新开始从0计数。这不是我们想要的。

程序即使结束,该计数器的值也存在,下次程序启动再会先加载该计数器的值并加1后再重新存储起来。

所以要建立一个配置文件,用于记录该软件的使用次数。

该配置文件使用键值对的形式。这样便于阅读数据,并操作数据。

键值对数据是map集合。数据是以文件形式存储,使用io技术。
那么map+io-->properties

配置文件可以实现应用程序数据的共享。
*/
import java.io.*;
import java.util.*;
class RunCount
{
public static void main(String[] args) throws IOException
{
Properties prop = new Properties();//创建集合对象

File file = new File("count.ini");//建立一个配置文件

if(!file.exists())
file.createNewFile();
//将文件跟读取流进行关联
FileInputStream fis = new FileInputStream(file);

prop.load(fis);//加载流中的文件数据到集合中

int count = 0;//定义计数器
String value = prop.getProperty("time");//获取操作次数值

if(value!=null)//如果值不为null,则将其赋值给count
{
count = Integer.parseInt(value);
if(count>=5)
{
//如果程序被使用了超过5次,则终止使用,并提示信息
System.out.println("您好!使用次数已到,拿钱!");
return ;
}
}
count++;//程序每启动一次就自增一次
prop.setProperty("time",count+"");//将次数记录进集合
//创建输出流
FileOutputStream fos = new FileOutputStream(file);
//将集合中的数据通过输出流存入硬盘文件中
prop.store(fos,"");

fos.close();//关流
fis.close();
}
}

十二、打印流
该流提供了打印方法,可以将各种数据类型的数据都原样打印。

字节打印流:PrintStream 
构造函数可以接收的参数类型
1、file对象,file
2、字符串路径,String.
3、字节输出流。OutputStream

字符打印流:PrintWriter  
构造函数可以接收的参数类型:
1、file对象,file
2、字符串路径,String.
3、字节输出流。OutputStream
4、字符输出流,Writer.

示例:
import java.io.*;
class PrintStreamDemo
{
public static void main(String[] args) throws IOException
{
//缓冲区关联键盘录入
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
//打印流关联文件,自动刷新,不用flush
PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true);

String line = null;
while ((line=bufr.readLine())!=null)
{
if("over".equals(line))//结束字符
break;
//out.write(line.toUpperCase());
out.println(line.toUpperCase());//转成大写并输出
//out.flush(); 如果上方PrintWriter方法里的,第二个参数是autoFlush 为true,则自动刷新。
}
out.close();//关流
bufr.close();
}
}

运行结果:
Java基础——IO流(下)之字节流

十三、序列流
SequenceInputStream  用于对多个流进行合并。
       常见合并多个流文件步骤:
        1、创建集合,并将流对象添加进集合
        2、创建Enumeration对象,将集合元素加入
        3、创建SequenceInputStream对象,合并流对象
        4、创建写入流对象,FileOutputStream关联写入文件
        5、利用SequenceInputStream对象和FileOutputStream对象读数据进行反复读写操作。
示例:

/*
需求:将三个文本文件中的数据合并到一个文本文件中
思路:1、创建Vector集合,将三个文本文件字节流添加到集合中
2、创建Enumeration对象,创建SequnceInputStream对象关联Enumeration
3、输出流关联新文本文件
4、反复读写操作
*/
import java.io.*;
import java.util.*;
class SequenceDemo
{
public static void main(String[] args) throws IOException
{
//创建vector
Vector<FileInputStream> v =new Vector<FileInputStream>();
//添加相关流对象
v.add(new FileInputStream("1.txt"));
v.add(new FileInputStream("2.txt"));
v.add(new FileInputStream("3.txt"));
//创建Enumeration枚举对象
Enumeration<FileInputStream> en = v.elements();
//合并流
SequenceInputStream sis = new SequenceInputStream(en);
//关联写入文件
FileOutputStream fos = new FileOutputStream("4.txt");

byte[] buf = new byte[1024];

int len = 0;
while ((len=sis.read(buf))!=-1)
{
fos.write(buf,0,len);//反复读写
}
fos.close();//关流
sis.close();
}
}
运行结果:
Java基础——IO流(下)之字节流

练习:切割文件 和 合并文件
/*
练习:切割文件 和 合并文件
需求:将一个图片文件切割成几部分,然后根据这个及部分合并成一张图片
*/
import java.io.*;
import java.util.*;
class SplitFile
{
public static void main(String[] args) throws IOException
{
//SplitFile();//切割方法
merge();//合并方法
}
//切割方法
public static void SplitFile() throws IOException
{
//关联要切割的文件
FileInputStream fis = new FileInputStream("1.bmp");

FileOutputStream fos = null;
//定义1M大小存储容器
byte[] buf = new byte[1024*1024];
int len = 0;
int count = 1;//定义1个计数器,作为部分编号
while ((len = fis.read(buf))!=-1)
{
//每满1M就写入一个新文件中
fos = new FileOutputStream("splitfiles\\"+(count++)+".part");
fos.write(buf,0,len);
fos.close();//每写完一个部分文件要关流
}
fis.close();
}
//合并方法
public static void merge() throws IOException
{
//定义一个集合存储这些部分文件并关联路径数据
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for (int x= 1; x<5 ; x++ )
{
al.add(new FileInputStream("splitfiles\\"+x+".part"));
}

final Iterator<FileInputStream> it = al.iterator();
//由于Enumeration是Vector的迭代方法,这里创建一个Enumeration类型的匿名内部类
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>()
{
public boolean hasMoreElements()
{
return it.hasNext();
}
public FileInputStream nextElement()
{
return it.next();
}
};
//关联Enumeration对象
SequenceInputStream sis = new SequenceInputStream(en);
//将合并的文件数据写入指定文件中
FileOutputStream fos = new FileOutputStream("splitfiles\\tupian.bmp");

byte[] buf = new byte[1024];
int len= 0;
while ((len= sis.read(buf))!=-1)
{
fos.write(buf,0,len);//边读边写
}
fos.close();//关流
sis.close();
}
}