引言
JVM对于文件的操作只有增删查是远远不够的,想想除了以上几个主要功能外,我们还需要对文件实现改动即编辑文件,而编辑文件必然涉及到数据流,通常在java中,数据流包含字节流和字符流,字节流和字符流的继承框架体系如下所示。
在众多的流当中,InputStream、OutputStream、Reader、Writer为众多流中最顶层的父类,在这众多的流中,最常见的流已在上图中用红框标识出,首先小猿来复习字节流有关的知识,在操作系统中,我们可以把一切皆当成文件对象,而文件对象又分为具体文件和文件夹,而众多流所要面对的对象都是具体文件,下文就对具体文件的改动操作进行重点论述。
字节流
在字节流中中需要注意字以下几个问题:
1、如何提高字节流读写效率
2、字节流如实实现续写
3、字节流如何换行(分操作系统)
4、在字节输出流中flush和close的方法的区别
上面几个问题是字节流的操作的核心问题
对于问题1,其本质的问题在于字节流每次只能读取一个字节,若文件大小为10241024Byte,那么该算法就得执行10241024次,时间复杂度太高,导致文件读写效率低下,此时我们考虑能否适量提高空间复杂度来换取程序时间复杂度的降低,答案明显是有的,设置缓冲数组来读写文件,对于一个1M的文件若设定的缓冲数组大小是1024Byte,那么至少需要读写的次数为1024,理论上时间复杂度比前例减少了1024倍。
针对问题2,只要打开append开关就可实现续写,append是布尔类型的,所以只要设定append开关为true就可以了。
对于问题3,Linux中遇到换行符("\n")会进行回车+换行的操作,而在windows中要回车符+换行符("\r\n")才会回车+换行,在Mac中则是以("\r")为回撤+换行的操作。
针对问题4,在输出流的flush方法为将内存中的数据刷新到磁盘上,而close则是先执行刷新操作,然后将数据刷新到磁盘。
notes:字节缓冲流的默认大小是8192Bytes
案例一
音乐串烧案例,要求两首音乐相互间隔播放,播放的间隔时间为10秒钟,案例实现代码如下所示。
public class Demo03MehodHomeWork {
public static void main(String[] args) throws IOException {
String path="G:\\testfile";
String src1="\\杨宗纬;张碧晨 - 凉凉.mp3";
String src2="\\刘珂矣 - 一袖云.mp3";
String dest1="\\file1.mp3";
String dest2="\\file2.mp3";
String dest="\\dest3.mp3";
File f1= new File(path + src1);
File f2= new File(path + src2);
BufferedInputStream bis1 = new BufferedInputStream(new FileInputStream(path + src1));
BufferedInputStream bis2 = new BufferedInputStream(new FileInputStream(path + src2));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path + dest, true));
int f1Length = 0;
int f2Length =0;
//判断文件是否存在,否则抛异常
if(f1.exists()&&f2.exists()){
f1Length = (int) f1.length();
f2Length = (int) f2.length();
}else {
throw new IOException("file may not exists ");
}
//两首歌曲baudrate不一致
int songBaudrate1=320;
int songBaudrate2=320;
//创建时间起始坐标,使得文件不超过4960KBytes,相当于文件分割定位
int[] timeStamp1 = new int[30];
for (int i = 0,j=0;j<=244 &&i < timeStamp1.length; i++) {
j=0+i*10;
timeStamp1[i] =j;
}
int[] timeStamp2 = new int[30];
//创建时间起始坐标,使得文件不超过4960KBytes,相当于文件按照时间分割定位
for (int i = 0,j=0;j<252 &&i < timeStamp2.length; i++) {
j=10+i*10;
timeStamp2[i] = j;
}
//System.out.println("-----------------------");
int file_counter1=0,file_counter2=0;
//int counter=0;
byte[] bytes = new byte[256];
int len=0;
int index1=0,index2=0;
int totalCounter=0;
int totalSize = (songBaudrate2*244+songBaudrate1*242)*1024/8;
boolean flag=true;
while (true){
//文件大小不能超过超过设定大小
if(totalCounter>totalSize){
break;
}
if(flag){
int start=songBaudrate2*1024*timeStamp1[index1]/8;
int end=songBaudrate2*1024*timeStamp1[index1+1]/8;
if(end<start){
break;
}
while ((len=bis1.read(bytes))!=-1){
if(file_counter1>start&& file_counter1<end){
bos.write(bytes,0,len);
}
file_counter1+=len;
if(file_counter1>(songBaudrate2*1024*timeStamp1[index1+1]/8)){
for (int i = 0; i < bytes.length; i++) {
bytes[i]=0;
}
bos.flush();
len=0;
totalCounter+=(end-start);
break;
}
}
index1++;
flag = false;
}else{
int start=songBaudrate1*1024*timeStamp2[index2]/8;
int end=songBaudrate1*1024*timeStamp2[index2+1]/8;
if(end<start){
break;
}
while ((len=bis2.read(bytes))!=-1){
if(file_counter2>start&&
file_counter2<end){
bos.write(bytes,0,len);
//bos.flush();
}
file_counter2+=len;
if(file_counter2>end){
//清空数组
for (int i = 0; i < bytes.length; i++) {
bytes[i]=0;
}
bos.flush();
len=0;
totalCounter+=(end-start);
break;
}
}
index2++;
flag=true;
}
//System.out.println("----------------------------");
}
bis1.close();
bis2.close();
bos.close();
}
}
代码已成功测试,dest3已经成功完成
可以正常播放,代码还必定有优化的地方,请阅读本文的小伙伴们自行优化。此外,需要明确提出的是所串烧的音乐对象必须保持波特率一致,不然在串烧过程中出现合成音乐无法播。
上述案例基本上涵盖了数据流中常见的字节流,有输入和输出的字节流,字节缓冲输入和输出流。
字符流
在学习字符流案例之前,首先要弄明白以下几个问题?
1、为什么会出现字符流?
2、什么是字节流?
3、字符流的出现能解决那些问题?
对于问题1,通常情况下以字节直接读取字符,由于编码规则的不一致会引起乱码,为解决出现乱码的问题,字符流就由此而生。
对于问题2,这里讲的比较接地气,那么字符流可以总结为:
字符流=字节流+编码表
对于问题3,引入字符流后,只要正确的配置编码规则,就可以有效防止中文或其他字符的乱码,帮助人们去更加有效操作字符。
对于问题3,通常读写字符的
notes:字符串的编码方式和解码方式必须一致,否则也会出现乱码问题,字符缓冲流的默认大小是8192Bytes。
案例二 字符流复制java文件
public class CharBuffer {
public static void main(String[] args) throws IOException {
//copyFile01();
//copyFile02();
copyFile03();
}
public static void copyFile01() throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("reader&writer\\src\\charbuffer\\CharBuffer.java"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("reader&writer\\CharBuffer01.java"));
//读写数据,复制文件
//一次读写一个字符数据
/*int ch;
while ((ch=isr.read())!=-1) {
osw.write(ch);
}*/
char[] chars = new char[1024];
int len;
while((len = isr.read(chars)) !=-1){
osw.write(chars,0,len);
}
osw.flush();
isr.close();
osw.close();
}
//采用filereader 和filewriter来实现
public static void copyFile02() throws IOException {
FileReader fr = new FileReader("reader&writer\\src\\charbuffer\\CharBuffer.java");
FileWriter fw = new FileWriter("reader&writer\\CharBuffer02.java");
/*int ch;
while((ch=fr.read())!=-1){
fw.write(ch);
}*/
int len;
char[] chars = new char[1024];
while ((len=fr.read(chars))!=-1){
fw.write(chars,0,len);
}
fw.flush();
fr.close();
fw.close();
}
//字符缓冲流复制java文件
public static void copyFile03() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("reader&writer\\src\\charbuffer\\CharBuffer.java"));
BufferedWriter bw = new BufferedWriter(new FileWriter("reader&writer\\CharBuffer03.java"));
BufferedReader br1 = new BufferedReader(new FileReader("reader&writer\\CharBuffer03.java"));
char [] chars = new char[1024];
int len;
while ((len = br.read(chars))!=-1){
bw.write(chars,0,len);
}
bw.write("hello\r\n");
bw.write("world\r\n");
bw.flush();
//特殊方式写入
for (int i = 0; i < 10; i++) {
bw.write("hello" + i);
//bw.write("\r\n");
bw.newLine();
bw.flush();
}
bw.close();
br.close();
//FileReader 特有方式读取
String line;
while ((line=br1.readLine())!=null){
System.out.println(line);
}
System.out.println("-----------------------");
br1.close();
}
}
notes 需要注意的是在写入换行符的时候采用bw.write("\r\n")时,则会降低代码的移植性,最好采用bw.newLine()来加入换行效果则更好
运行结果
案例三 点名器案例
案例要求是先根据arraylist将其中的数据写入到txt文件中,然后将txt文件的内容读出后存储到arraylist集合中,再根据随机函数实现点名
student类
public class Student {
private String sid;
private String name;
private int age;
private String address;
public Student() {
}
public Student(String sid, String name, int age, String address) {
this.sid = sid;
this.name = name;
this.age = age;
this.address = address;
}
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"sid='" + sid + '\'' +
", name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
测试类
public class FileOperation {
public static void main(String[] args) throws IOException {
//arrayList2File();
ArrayList<Student> students = file2ArrayList();
ArrayList<Student> arrayList = sortArrayList(students);
callNameDemo(arrayList);
}
//将arraylist中的文件写到txt文件中
public static void arrayList2File() throws IOException {
ArrayList<Student> students = new ArrayList<>();
Student s1 = new Student("001", "张君", 27, "北京");
Student s2 = new Student("002", "唐三", 20, "唐门");
Student s3 = new Student("003", "石大力", 22, "沧澜圣地");
Student s4 = new Student("004", "陈思思", 22, "天音宗");
Student s5 = new Student("005", "林动", 22, "浮屠道门");
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
students.add(s5);
BufferedWriter bw = new BufferedWriter(new FileWriter("reader&writer\\CharBuffer.txt"));
for (Student s :
students) {
StringBuilder sb = new StringBuilder();
sb.append(s.getSid()).append(",").append(s.getName()).append(",").append(s.getAge()).append(",")
.append(s.getAddress());
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
bw.close();
}
//将txt文件中的文件读取后存储到ArrayList中
public static ArrayList<Student> file2ArrayList() throws IOException {
System.out.println("---------------------开始读取文件---------------");
BufferedReader br = new BufferedReader(new FileReader("reader&writer\\CharBuffer.txt"));
ArrayList<Student> students = new ArrayList<>();
String line;
while ((line = br.readLine())!=null){
String[] split = line.split(",");
Student s = new Student();
s.setSid(split[0]);
s.setName(split[1]);
s.setAge(Integer.parseInt(split[2]));
s.setAddress(split[3]);
students.add(s);
}
br.close();
for (Student s :
students) {
System.out.println(s.getSid()+","+s.getName()+","+s.getAge()+","+s.getAddress());
}
System.out.println("---------------------文件读取结束---------------");
return students;
}
//按照年龄需要对输出的文件进行改进
public static ArrayList<Student> sortArrayList(ArrayList<Student> arrayList) throws IOException {
System.out.println("---------------生成排序文件-----------------");
BufferedWriter bw = new BufferedWriter(new FileWriter("reader&writer\\CharBuffer2.txt"));
Collections.sort(arrayList,(o1, o2)->{
return o1.getAge()-o2.getAge() >=0 ? 1:-1;
});
for (Student s :
arrayList) {
StringBuilder sb = new StringBuilder();
sb.append(s.getSid()).append(",").append(s.getName()).append(",").append(s.getAge()).append(",")
.append(s.getAddress());
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
arrayList.forEach(m-> System.out.println(m));
bw.close();
System.out.println("---------------排序文件生成结束----------------");
return arrayList;
}
//开始点名
public static void callNameDemo(ArrayList<Student> list){
System.out.println("--------------------开始点名------------------");
Random r = new Random();
int index = r.nextInt(list.size());
Student s = list.get(index);
System.out.println("幸运者是:"+s.getName());
System.out.println("--------------------点名结束------------------");
}
}
运行结果
至此,文件的操作就告一段落了,下一篇博文继续。