1.IO流
Java对数据的操作是通过流的方式
流按流向分为两种:输入流,输出流。
流按操作类型分为两种:
字节流 : 字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的
字符流 : 字符流只能操作纯字符数据,比较方便。
IO流常用父类
字节流的抽象父类:
InputStream
OutputStream
字符流的抽象父类:
Reader
Writer
2.FileInputStream和FileOutputStream
FileInputStream
从文件系统中的某个文件中获得输入字节
int | read() 从此输入流中读取一个数据字节。 |
read()方法根据编码表读取内容。如果读取不到的时候是-1,根据读取不到的标识是-1可以写出如下代码
FileInputStream fis = new FileInputStream("xxx.txt");
int b;
while ((b = fis.read()) != -1) {
System.out.println(b);
}
fis.close();
需要注意的是,在于-1比较的过程中,需要用变量接受值进行比较,方便输出。如果写为如下形式
while (fis.read() != -1) {
System.out.println(fis.read());
}
那么就只能读取到固定的第一个数。
read的返回值为什么是int而不是byte?
因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有
可能在读到中间的时候遇到111111111
那么这11111111是byte类型的-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收,如果11111111会在其前面补上
24个0凑足4个字节,那么byte类型的-1就变成int类型的255了这样可以保证整个数据读完,而结束标记的-1就是int类型
FileOutPutStream
FileOutputStream fos = new FileOutputStream("yyy.txt");
fos.write(97);
fos.write(98);
fos.write(99);
fos.close();
在写入的时候,虽然是int,但在文件上会把钱24位删掉,只留一个byte。
在创建对象的时候,如果存在文件,会清空再写入(出现类似于覆盖的效果),如果不存在文件,就会创建出来再写入。
如果想要续写,需要调用另一个构造方法
在构造FileOutputStream的时候,参数列表多加一个true。
文件拷贝:
public static void main(String[] args) throws IOException {
// demo1();
FileInputStream fis = new FileInputStream("copy.jpg");
FileOutputStream fos = new FileOutputStream("copy1.jpg");
byte[] arr = new byte[fis.available()];
fis.read(arr);
fos.write(arr);
fos.close();
fis.close();
}
public static void demo1() throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream("双元.jpg");
FileOutputStream fos = new FileOutputStream("copy.jpg");
int a = 0;
while ((a = fis.read()) != -1) {
fos.write(a);
}
fos.close();
fis.close();
}
第一种:拷贝音视频等大型文件很慢,因为没有“篮子”,买东西要一个一个的买,多跑了很多路。所以不能直接这么用。
第二种:先用available获取可用字节,然后创建一个可用字节大小的byte数组,输入流读取对应长度的数据,通过输出流全部一次性写入文件中。但是,jvm虚拟机以前64m,后来也才170多m,很明显在大型文件拷贝的时候不够用,很容易溢出,所以也不推荐。
第三种是定义小数组,将文件分为若干个小数组,分批次写入,案例代码如下:
public static void demo1() throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream("yyy.txt");
FileOutputStream fos = new FileOutputStream("ooo.txt");
byte[] arr = new byte[1024 * 8];
int len;
while ((len = fis.read(arr)) != -1) {
fos.write(arr, 0, len);
}
fos.close();
fis.close();
}
}
一次操作做8k。
3.BufferInputStream和BufferOutputStream
装饰器模式就是这里。
BufferInputStream和BufferOutputStream分别装饰了FileInputStream和FileOutPutStream
public static void main(String[] args) throws IOException {
// demo1();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("双元.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("xuexi.jpg"));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
bos.close();
bis.close();
}
public static void demo1() throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream("致青春.mp3");
FileOutputStream fos = new FileOutputStream("music.mp3");
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
bos.close();
bis.close();
}
底层代码:
可以发现,BufferedInputStream和BufferOutputStream的缓冲都是8k,和小数组一样。
那么问题来了,二者效率哪个高?
其实速度差不多,但是因为小数组的读写用的是同一个数组,所以效率会更高一点点。
二者的缓冲区实现原理:
读:BufferedInputStream会一次性从文件中读取8192个, 存在缓冲区中, 每次返回给程序一个
程序再次读取时, 就不用找文件了, 直接从缓冲区中获取,直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个
写:程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中
直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里
分过程如上所述,总过程就是从硬盘中的文件一次读取8k到缓冲区,然后在内存中再一个个从缓冲区搬运到程序中,需要取出来也是这样,先一个个的取到缓冲区,再一次性全放进硬盘。相比于单纯的一个个字节读写,多了个整存整取,但是因为红框内,也就是大部分操作放在内存中,所以,整个流程比单纯字节存取要快得多。
flush()和close()的区别?
flush()是用来刷新缓冲区的,刷新之后还可以再次写入
close()是用来关闭流的如果是带有缓冲区的流,不但会关闭流,释放资源,还会在关闭之前刷新缓冲区,不能再写出。
字节流操作中文
输入流操作中文很困难,GBK一个汉字两个字符,UTF-8一个汉字三个字符,而且有标点符号,不管设置缓冲区多大,很容易读汉子读一半。
输出流操作中文也比较麻烦,需要把字符串用getBytes()转换成字节数组才能写入。
4.字节流的一些实际问题
流的标准异常处理代码JDK6及以前
public class Demo7_TryFinally {
public static void main(String[] args) throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("haha.txt");
fos = new FileOutputStream("xixi.txt");
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
} finally {
try {
if (fos != null)
fos.close();
} finally {
if (fis != null)
fis.close();
}
}
}
}
找不到也要可以关闭流,所以有了第一个tryfinally,因为要关闭,所以流定义在try外面。
下面两个流能关一个是一个,所以也使用try...finally。
流的标准异常处理代码JDK7以后
try (FileInputStream fis = new FileInputStream("haha.txt");
FileOutputStream fos = new FileOutputStream("xixi.txt");) {
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
}
}
try的小括号内定义流,下面语句执行完毕,上面的流自动关闭。
因为从JDk1.7开始,字节流实现了AutoCloseable接口。
图片加密:
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jiami.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("jiami2.jpg"));
int b;
while ((b = bis.read()) != -1) {
bos.write(b ^ 123);
}
bos.close();
bis.close();
}
输入流正常,输出流对读取到的内容进行异或处理,文件就无法打开。将输出流换到输入流位置生成的输出流文件即可正常打开,因为一个数异或同一个数两次,得到的就是他本身。
拷贝文件到当前项目中
public class Test2 {
/**
* 在控制台录入文件的路径,将文件拷贝到当前项目下
*
* 分析:
*
* 1,定义方法对键盘录入的路径进行判断,如果是文件就返回
* 2,在主方法中接收该文件
* 3,读和写该文件
*
* @throws IOException
*/
public static void main(String[] args) throws IOException {
File file = getFile();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getName()));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
bos.close();
bis.close();
System.out.println("恭喜您!拷贝完成!");
}
@SuppressWarnings("resource")
public static File getFile() {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请您输入想要拷贝的文件所在路径:");
String nextLine = sc.nextLine();
File file = new File(nextLine);
if (!file.exists()) {
System.out.println("您输入的文件路径不存在,请重新输入~");
} else if (file.isDirectory()) {
System.out.println("您输入的文件是个文件夹,无法完成拷贝!请重新输入!");
} else {
return file;
}
}
}
}
拷贝指定路径下的文件到当前项目下。
把键盘录入的内容写入到文件中。
案例代码:
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("text.txt"));
System.out.println("请输入您想要录入的数据:");
while (true) {
String nextLine = sc.nextLine();
if ("quit".equals(nextLine)) {
System.out.println("感谢使用!录入结束~");
break;
}
bos.write(nextLine.getBytes());
bos.write("\r\n".getBytes());
}
bos.close();
}
5.字符流
输入流
FileReader fr = new FileReader("text.txt");
int b;
while ((b = fr.read()) != -1) {
System.out.print((char) b);
}
fr.close();
输出流:
FileWriter fw = new FileWriter("yyy.txt");
fw.write("大吉大利,今晚吃鸡");
fw.close();
拷贝文件:
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("xxx.txt");
FileWriter fw = new FileWriter("zzz.txt");
int b;
while ((b = fr.read()) != -1) {
fw.write(b);
}
fw.close();
fr.close();
}
}
Writer里面存在一个2k的缓冲区,如果不关闭流,内容就写在缓冲区里面。
程序需要读取一段或者写出一段文本的时候使用字符流。
因为读取不会出现乱码,写入的时候可以一下写入一个字符串。
字符流是否可以拷贝非纯文本的文件?
读的时候字节转换为字符,取得时候字符是乱码,无法转换回来。
自定义字符数组的拷贝
public static void demo2() throws FileNotFoundException, IOException {
FileReader fr = new FileReader("xxx.txt");
FileWriter fw = new FileWriter("ooo.txt");
char[] arr = new char[1024];
int b;
while ((b = fr.read(arr)) != -1) {
fw.write(arr, 0, b);
}
fw.close();
fr.close();
}
带缓冲的字符流:
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("text.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("aaa.txt"));
int b;
while ((b = br.read()) != -1) {
bw.write(b);
}
bw.close();
br.close();
}
readLine()和newLine()
readLine()是BufferedReader的方法,可以一次性读取一整行
newLine()是BufferedWriter的方法,可以换行。
案例代码如下:
public static void demo2() throws FileNotFoundException, IOException {
BufferedReader br = new BufferedReader(new FileReader("text.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("qaq.txt"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
bw.close();
br.close();
}
public static void demo1() throws FileNotFoundException, IOException {
BufferedReader br = new BufferedReader(new FileReader("text.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
newLine()和\r\n的区别?
newLine()全平台通用
\r\n只有在windows下有效。
LineNumberReader:
public static void main(String[] args) throws IOException {
LineNumberReader lnr = new LineNumberReader(new FileReader("text.txt"));
String line;
lnr.setLineNumber(5);
while ((line = lnr.readLine()) != null) {
System.out.println(lnr.getLineNumber() + ":" + line);
}
lnr.close();
}
主要掌握setLineNumber和getLineNumber两种方法。readLine底层是Number++,所以getLineNumber是在setLineNumber基础上加1开始的。
6.装饰设计模式
前面的几个类,就用到了装饰设计模式。如FileReader可以被BufferedReader,LineNumberReader包装。
案例代码:
interface Coder {
public void code();
}
class Student implements Coder {
@Override
public void code() {
System.out.println("我只会基础");
}
}
class JinXiuXueSheng implements Coder {
private Student s;
public JinXiuXueSheng(Student s) {
this.s = s;
}
@Override
public void code() {
s.code();
System.out.println("我又学会了前端");
System.out.println("我还会各种框架");
System.out.println("我还能搞大数据!");
}
}
这是一个自定义的装饰器模式。基本流程是:
1.获取被装饰类的引用
2.将被装饰类的对象传入构造方法中。
3.对原有功能进行增强。
7.使用指定码表读写字符
InputStreamReader,OutputStreamWriter都是字符流,但是里面的参数需要的是字节流,这和它的转换流程有关。
案例代码:
public static void demo3() throws UnsupportedEncodingException, FileNotFoundException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("GBK.txt"), "GBK"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("UTF-8_2.txt"), "UTF-8"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
}
bw.close();
br.close();
}
public static void demo2() throws UnsupportedEncodingException, FileNotFoundException, IOException {
// 对大量数据支持不够好
InputStreamReader isr = new InputStreamReader(new FileInputStream("GBK.txt"), "GBK");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("UTF-8_1.txt"), "UTF-8");
int b;
while ((b = isr.read()) != -1) {
osw.write(b);
}
osw.close();
isr.close();
}
public static void demo1() throws FileNotFoundException, IOException {
// 出现乱码,因为码表字符不同
FileReader fr = new FileReader("GBK.txt");
FileWriter fw = new FileWriter("UTF-8.txt");
int b;
while ((b = fr.read()) != -1) {
fw.write(b);
}
fr.close();
fw.close();
}
转换流程图解如下:
8.两个小案例
统计文件内不同字符出现的次数,并把它们输出在指定的文件内。
案例代码:
public class Test2 {
/**
* 获取一个文本上每个字符出现的次数,将结果写在times.txt上
*
* 分析:
* 1,创建带缓冲的输入流对象
* 2,创建双列集合对象TreeMap
* 3,将读到的字符存储在双列集合中,存储的时候要做判断,如果不包含这个键,就将键和1存储,如果包含这个键,就将该键和值加1存储
* 4,关闭输入流
* 5,创建输出流对象
* 6,遍历集合将集合中的内容写到times.txt中
* 7,关闭输出流
*
* @throws IOException
*/
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("zzz.txt"));
TreeMap<Character, Integer> tm = new TreeMap<>();
int b;
while ((b = br.read()) != -1) {
char ch = (char) b;
tm.put(ch, !tm.containsKey(ch) ? 1 : tm.get(ch) + 1);
}
br.close();
BufferedWriter bw = new BufferedWriter(new FileWriter("times.txt"));
for (Character key : tm.keySet()) {
switch (key) {
case '\t':
bw.write("\\t" + "=" + tm.get(key));
break;
case '\r':
bw.write("\\r" + "=" + tm.get(key));
break;
case '\n':
bw.write("\\n" + "=" + tm.get(key));
break;
default:
bw.write(key + "=" + tm.get(key));
}
bw.newLine();
}
bw.close();
}
}
需要注意两点:
读取到的自动转换为int,存储的时候要以字符的形式存储,所以需要强转。
换行,制表符,回车显示都一样,这里需要switch语句判断,分情况显示。
试用版软件功能实现:
案例代码:
public class Test3 {
/**
* 当我们下载一个试用版软件,没有购买正版的时候,每执行一次就会提醒我们还有多少次使用机会用学过的IO流知识,模拟试用版软件,
* 试用10次机会,执行一次就提示一次您还有几次机会,如果次数到了提示请购买正版
*
* @throws IOException
* 分析:
* 1,创建带缓冲的输入流对象,因为要使用readLine方法,可以保证数据的原样性
* 2,将读到的字符串转换为int数
* 3,对int数进行判断,如果大于0,就将其--写回去,如果不大于0,就提示请购买正版
* 4,在if判断中要将--的结果打印,并将结果通过输出流写到文件上
*/
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("config.txt"));
String readLine = br.readLine();
br.close();
int times = Integer.parseInt(readLine);
if (times > 0) {
System.out.println("您还有" + times-- + "次使用机会~");
FileWriter fw = new FileWriter("config.txt");
fw.write(times + "");
fw.close();
} else {
System.out.println("您的使用次数已经没有了!请购买正版软件!");
}
}
}
9.递归
递归就是方法自己不断地调用自己。
递归的好处:不用知道具体循环的次数
坏处:调用过多栈内存溢出
Exception in thread "main" java.lang.StackOverflowError
构造方法可以递归调用吗?
不可以,无穷匮也,停不下来。
递归一定有返回值吗?
可以有也可以没有
10.输入文件夹路径,遍历展示该文件夹下所有的文件
public class Test4 {
/**
* 需求:从键盘输入接收一个文件夹路径,打印出该文件夹下所有的.java文件名
*
* 分析:
* 从键盘接收一个文件夹路径
* 1,如果录入的是不存在,给与提示
* 2,如果录入的是文件路径,给与提示
* 3,如果是文件夹路径,直接返回
*
* 打印出该文件夹下所有的.java文件名
* 1,获取到该文件夹路径下的所有的文件和文件夹,存储在File数组中
* 2,遍历数组,对每一个文件或文件夹做判断
* 3,如果是文件,并且后缀是.java的,就打印
* 4,如果是文件夹,就递归调用
*/
public static void main(String[] args) {
File dir = getDir();
printJavaFile(dir);
}
public static void printJavaFile(File dir) {
File[] files = dir.listFiles();
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".java")) {
System.out.println(file.getAbsolutePath());
} else if (file.isDirectory()) {
printJavaFile(file);
}
}
}
@SuppressWarnings("resource")
public static File getDir() {
Scanner sc = new Scanner(System.in);
System.out.println("请您输入想要检索的文件夹路径:");
while (true) {
String path = sc.nextLine();
File file = new File(path);
if (!file.exists()) {
System.out.println("您输入的文件路径不存在,请重新输入!");
} else if (file.isFile()) {
System.out.println("您输入的是文件路径,请重新输入!");
} else {
return file;
}
}
}
}
先确保是文件夹路径而不是文件路径或者其他,再判断,是文件夹就递归,不是文件夹就遍历该文件夹下的文件。
11.序列流
将多个字节流整合在一起。
案例代码:
public static void demo2() throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream("xxx.txt");
FileInputStream fis2 = new FileInputStream("text.txt");
SequenceInputStream sis = new SequenceInputStream(fis, fis2);
FileOutputStream fos = new FileOutputStream("qq.txt");
int b;
while ((b = sis.read()) != -1) {
fos.write(b);
}
sis.close();
fos.close();
}
拼接多个需要使用Vector接受枚举类型,再把elements传入SequenceInputStream
public static void demo3() throws FileNotFoundException, IOException {
Vector<FileInputStream> vec = new Vector<>();
vec.add(new FileInputStream("a.txt"));
vec.add(new FileInputStream("b.txt"));
vec.add(new FileInputStream("c.txt"));
Enumeration<FileInputStream> elements = vec.elements();
SequenceInputStream sis = new SequenceInputStream(elements);
FileOutputStream fos = new FileOutputStream("d.txt");
int b;
while ((b = sis.read()) != -1) {
fos.write(b);
}
fos.close();
sis.close();
}
后面文件为什么是继续写而不是清空??
因为输出流的把文件完成清空的语句,是new这一下,而不是后面的write
12.内存输出流
可以向内存中写数据,把内存当做一个缓冲区,
关闭ByteArryOutputStream是没有意义的,它本身是可以增长的内存数组。
共两种方式,一种是toByteArray,一种是直接toString。
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("qq.txt");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b;
while ((b = fis.read()) != -1) {
baos.write(b);
}
// byte[] array = baos.toByteArray();
// System.out.println(new String(array));
System.out.println(baos.toString());
fis.close();
}
内存输出流笔试题:
public class Test1 {
/**
* @param args
* 定义一个文件输入流,调用read(byte[] b)方法,将a.txt文件中的内容打印出来(byte数组大小限制为5)
*
* 分析:
* 1,reda(byte[] b)是字节输入流的方法,创建FileInputStream,关联a.txt
* 2,创建内存输出流,将读到的数据写到内存输出流中
* 3,创建字节数组,长度为5
* 4,将内存输出流的数据全部转换为字符串打印
* 5,关闭输入流
* @throws IOException
* @throws IO
*/
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("qq.txt");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] arr = new byte[5];
int b;
while ((b = fis.read(arr)) != -1) {
baos.write(arr, 0, b);
}
System.out.println(baos);
fis.close();
}
}
13.随机访问流
RandomAccessFile不属于流,它是Object类的子类。
public static void main(String[] args) throws IOException {
RandomAccessFile raf = new RandomAccessFile("e.txt", "rw");
// raf.write(97);
// System.out.println(raf.read());
raf.seek(4);
raf.write(89);
raf.close();
}
void | seek(long pos) 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。 |
14.ObjectOutputStream和ObjectInputStream
这个流可以将一个对象写出到文件中,或者把一个对象从文件中读取出来,写到文件中叫序列化,从文件中读叫反序列化。
序列化案例代码:
public static void main(String[] args) throws IOException {
Person p1 = new Person("zhangsan", 16);
Person p2 = new Person("lisi", 18);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("xinxi.txt"));
oos.writeObject(p1);
oos.writeObject(p2);
oos.close();
}
这样是不成的,因为这个类没有实现可序列化接口,会报出如下错误:
Exception in thread "main" java.io.NotSerializableException: com.heima.bean.Person
解决方案就是让javabean实现Serializable接口
读取的代码如下:
public class Demo4_ObjectInputStream {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("xinxi.txt"));
Object p1 = ois.readObject();
Person p2 = (Person)ois.readObject();
Object p3 = ois.readObject();
System.out.println(p1);
System.out.println(p2);
System.out.println(p3);
ois.close();
}
}
读取出来的数据是Object对象,需要强转类型。如果读取超出范围,会报出如下错误:
Exception in thread "main" java.io.EOFException
当输入过程中意外到达文件或流的末尾时,抛出此异常。有些流则会正常返回一个特殊值,比如null和-1.
解决方案代码:
public static void demo2() throws IOException, FileNotFoundException {
Person person = new Person("zhangsan", 18);
Person person2 = new Person("lisi", 19);
Person person3 = new Person("wangwu", 20);
Person person4 = new Person("zhangliu", 21);
ArrayList<Person> list = new ArrayList<>();
list.add(person);
list.add(person2);
list.add(person3);
list.add(person4);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("xinxi.txt"));
oos.writeObject(list);
oos.close();
}
public static void demo2() throws IOException, FileNotFoundException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("xinxi.txt"));
ArrayList<Person> list = (ArrayList<Person>) ois.readObject();
for (Person person : list) {
System.out.println(person);
}
ois.close();
}
如上图的读写。
将对象存在list中进行读取。
JavaBean实现序列化接口,版本ID
private static final long serialVersionUID = 1L;
报错的时候可以方便知道问题出在哪个版本。
15.数据输入输出流
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型
但是他不一定是线程安全的
案例代码:
public static void demo2() throws FileNotFoundException, IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("dataIO.txt"));
int x = dis.readInt();
int y = dis.readInt();
int z = dis.readInt();
System.out.println(x);
System.out.println(y);
System.out.println(z);
dis.close();
}
public static void demo1() throws FileNotFoundException, IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("dataIO.txt"));
dos.writeInt(997);
dos.writeInt(998);
dos.writeInt(999);
dos.close();
}
16.打印流
print和println都属于printStream
PrintStream和PrintWriter分别是打印的字节流和字符流,只操作数据目的的
printStream案例代码:
public static void demo1() {
System.out.println("aaa");
PrintStream ps = System.out; //获取标注输出流
ps.println(97); //底层通过Integer.toString()将97转换成字符串并打印
ps.write(97); //查找码表,找到对应的a并打印
Person p1 = new Person("张三", 23);
ps.println(p1); //默认调用p1的toString方法
Person p2 = null; //打印引用数据类型,如果是null,就打印null,如果不是null就打印对象的toString方法
ps.println(p2);
ps.close();
}
标准输入输出流
案例代码:
public static void main(String[] args) throws IOException {
InputStream is = System.in;
int read = is.read();
System.out.println(read);
is.close();
InputStream is2 = System.in;
int read2 = is2.read();
System.out.println(read2);
}
流在关闭之后,第二次会报错:
Exception in thread "main" java.io.IOException: Stream closed
改变标准输入输出流:
public static void demo2() throws FileNotFoundException, IOException {
System.setIn(new FileInputStream("qqq.txt")); // 改变标准输入流
System.setOut(new PrintStream("o.txt")); // 改变标准输出流
InputStream is = System.in;
PrintStream ps = System.out;
int b;
while ((b = is.read()) != -1) {
ps.write(b);
}
is.close();
ps.close();
}
输入流默认从键盘读取,输出流默认输出到控制台上,可以重新设置。。但是没卵用
两种键盘录入的方法:
public static void main(String[] args) throws IOException {
// BufferedReader br = new BufferedReader(new
// InputStreamReader(System.in));
// String readLine = br.readLine();
// System.out.println(readLine);
Scanner sc = new Scanner(System.in);
String nextLine = sc.nextLine();
System.out.println(nextLine);
}
17.properties类
它是hashtable的子类,同样是双列集合
properties表示了一个持久的属性集
properties可存在流中或者从流中读取
属性列表中每个键和值都是字符串
常用方法:
public Object setProperty(String key,String value)
public String getProperty(String key)
public Enumeration<String> stringPropertyNames() //返回此属性列表中的键集,其中该键及其对应值是字符串
Properties的load()和store()功能
load()从输入流中读取属性列表
store()将此 Properties
表中的属性列表(键和元素对)写入输出流。可以添加描述,也可以不添加。
案例代码:
public static void demo3() throws IOException, FileNotFoundException {
Properties p = new Properties();
p.load(new FileInputStream("config.properties"));
System.out.println(p);
p.setProperty("这个网站", "小时候常用");
p.store(new FileOutputStream("config.properties"), "第一个自定义配置文件");
System.out.println(p);
}
@SuppressWarnings("unchecked")
public static void demo2() {
Properties properties = new Properties();
properties.setProperty("姓名", "Yoo");
properties.setProperty("地址", "运城");
Enumeration<String> names = (Enumeration<String>) properties.propertyNames();
while (names.hasMoreElements()) {
String key = names.nextElement();
String value = properties.getProperty(key);
System.out.println(key + " " + value);
}
}
public static void demo1() {
Properties properties = new Properties();
properties.put("abc", 123);
System.out.println(properties);
}
18.递归的小练习
①统计文件夹的大小
public class Test1 {
/**
* @param args
* 需求:1,从键盘接收一个文件夹路径,统计该文件夹大小
*
* 从键盘接收一个文件夹路径
* 1,创建键盘录入对象
* 2,定义一个无限循环
* 3,将键盘录入的结果存储并封装成File对象
* 4,对File对象判断
* 5,将文件夹路径对象返回
*
* 统计该文件夹大小
* 1,定义一个求和变量
* 2,获取该文件夹下所有的文件和文件夹listFiles();
* 3,遍历数组
* 4,判断是文件就计算大小并累加
* 5,判断是文件夹,递归调用
*/
public static void main(String[] args) {
System.out.println("本系统用于查看文件夹内文件的总大小");
System.out.println("请输入您想要查看的文件夹路径:");
File file = getDir();
System.out.println("该文件夹大小为:" + getDirLength(file));
}
public static long getDirLength(File file) {
long len = 0;
File[] listFiles = file.listFiles();
for (File subFile : listFiles) {
if (subFile.isFile()) {
len = len + subFile.length();
} else if (subFile.isDirectory()) {
len = len + getDirLength(subFile);
}
}
return len;
}
@SuppressWarnings("resource")
public static File getDir() {
Scanner sc = new Scanner(System.in);
while (true) {
String line = sc.nextLine();
File file = new File(line);
if (!file.exists()) {
System.out.println("您输入的文件夹路径不存在!请重新输入!");
} else if (file.isFile()) {
System.out.println("您输入的不是文件夹路径,请重新输入!");
} else {
return file;
}
}
}
}
②删除指定文件夹
public class Test2 {
/**
* 需求:2,从键盘接收一个文件夹路径,删除该文件夹
*
* 删除该文件夹
* 分析:
* 1,获取该文件夹下的所有的文件和文件夹
* 2,遍历数组
* 3,判断是文件直接删除
* 4,如果是文件夹,递归调用
* 5,循环结束后,把空文件夹删掉
*/
public static void main(String[] args) {
System.out.println("请输入您想要删除的文件夹路径:");
File file = Test1.getDir();
deleteDir(file);
System.out.println("删除完成!");
}
public static void deleteDir(File file) {
File[] listFiles = file.listFiles();
for (File subFile : listFiles) {
if (subFile.isFile()) {
subFile.delete();
} else {
deleteDir(subFile);
}
}
file.delete();
}
}
③拷贝
public class Test3 {
/**
* 需求:3,从键盘接收两个文件夹路径,把其中一个文件夹中(包含内容)拷贝到另一个文件夹中
*
* 把其中一个文件夹中(包含内容)拷贝到另一个文件夹中
* 分析:
* 1,在目标文件夹中创建原文件夹
* 2,获取原文件夹中所有的文件和文件夹,存储在File数组中
* 3,遍历数组
* 4,如果是文件就用io流读写
* 5,如果是文件夹就递归调用
*
* @throws IOException
*/
public static void main(String[] args) throws IOException {
System.out.println("请输入源文件夹路径:");
File src = Test1.getDir();
System.out.println("请输入目标文件夹路径:");
File dest = Test1.getDir();
if (dest.equals(src)) {
System.out.println("目标文件和源文件相同!不可以拷贝!");
} else {
copy(src, dest);
System.out.println("拷贝完成");
}
}
public static void copy(File src, File dest) throws IOException {
// 1.在目标文件中创建源文件
File newdir = new File(dest, src.getName());
newdir.mkdir();
// 2.获取源文件中的文件和文件夹
File[] listFiles = src.listFiles();
// 3.遍历拷贝
for (File file : listFiles) {
if (file.isFile()) {
// 4.输出流的位置思路参考在目标文件中创建源文件的方法
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(new File(newdir, file.getName())));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
bos.close();
bis.close();
} else {
// 5.是文件夹的话继续递归调用
copy(file, newdir);
}
}
}
}
④按照层级打印文件夹下面的文件
public class Test4 {
/**
* 需求:4,从键盘接收一个文件夹路径,把文件夹中的所有文件以及文件夹的名字按层级打印, 例如:
* 把文件夹中的所有文件以及文件夹的名字按层级打印
* 分析:
* 1,获取所有文件和文件夹,返回的File数组
* 2,遍历数组
* 3,无论是文件还是文件夹,都需要直接打印
* 4,如果是文件夹,递归调用
* day07
* day08
* xxx.jpg
* yyy.txt
* Demo1_Consturctor.class
* Demo1_Consturctor.java
* Demo1_Student.class
* Demo1_Student.java
*/
public static void main(String[] args) {
System.out.println("请输入想要层级打印的文件夹路径:");
File dir = Test1.getDir();
printLev(dir, 0);
}
public static void printLev(File dir, int lev) {
// 1.获取文件夹和文件,返回数组
File[] listFiles = dir.listFiles();
// 2.对数组进行遍历
for (File file : listFiles) {
if (file.isFile()) {
for (int i = 0; i <= lev; i++) {
System.out.print(" ");
}
System.out.println(file);
} else {
printLev(file, lev + 1);
// 为什么不能使用lev++或者++lev?
// 那样会改变lev本身的值,进入子文件夹进行遍历,父文件夹中的方法lev也会+1,相当于从那以后父目录中的内容也都多一个
}
}
}
}
⑤斐波那契数列
public class Test5 {
/**
* * 不死神兔
* 故事得从西元1202年说起,话说有一位意大利青年,名叫斐波那契。
* 在他的一部著作中提出了一个有趣的问题:假设一对刚出生的小兔一个月后就能长成大兔,再过一个月就能生下一对小兔,并且此后每个月都生一对小兔,
* 一年内没有发生死亡,
* 问:一对刚出生的兔子,一年内繁殖成多少对兔子?
* 1 1 2 3 5 8 13 21
* 1 = fun(1)
* 1 = fun(2)
* 2 = fun(1) + fun(2)
* 3 = fun(2) + fun(3)
*/
public static void main(String[] args) {
// demo1();
int fun = fun(12);
System.out.println(fun);
}
public static void demo1() {
// 数组
int[] num = new int[12];
num[0] = 1;
num[1] = 1;
for (int i = 2; i < num.length; i++) {
num[i] = num[i - 2] + num[i - 1];
}
System.out.println(num[num.length - 1]);
}
public static int fun(int num) {
if (num == 1 || num == 2) {
return 1;
} else {
return fun(num - 2) + fun(num - 1);
}
}
}
⑥1000阶所有零和尾部零的个数(递归非递归两种)
public class Test6 {
/**
* @param args
* 需求:求出1000的阶乘所有零和尾部零的个数,不用递归做
* 472 249
*/
public static void main(String[] args) {
BigInteger b1 = new BigInteger("1");
for (int i = 1; i <= 1000; i++) {
BigInteger b2 = new BigInteger(i + "");
b1 = b1.multiply(b2);
}
// System.out.println(b1);
// 求所有零的个数
int count = 0;
String s = b1.toString();
for (int i = 0; i < s.length(); i++) {
if ('0' == s.charAt(i)) {
count++;
}
}
System.out.println("所有零的个数为:" + count);
// 求尾部零的个数
StringBuilder sb = new StringBuilder(s);
StringBuilder reverse = sb.reverse();
int num = 0;
for (int i = 0; i < sb.length(); i++) {
if ('0' != reverse.charAt(i)) {
break;
} else {
num++;
}
}
System.out.println("尾部零的个数为:" + num);
}
}
public class Test7 {
/**
* @param args
* 需求:求出1000的阶乘尾部零的个数,用递归做
* 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95
* 100...1000 1000 / 5 = 200
* 5 * 5 5 * 5 * 2 5 * 5 * 3 5 * 5 * 4 5 * 5 * 5 5 * 5 * 6 200 /
* 5 = 40
* 5 * 5 * 5 * 1 5 * 5 * 5 * 2 5 * 5 * 5 * 3 5 * 5 * 5 * 4 5 * 5
* * 5 * 5 5 * 5 * 5 * 6 5 * 5 * 5 * 7 5 * 5 * 5 * 8
* 40 / 5 = 8
* 5 * 5 * 5 * 5 8 / 5 = 1
*/
public static void main(String[] args) {
int fun = fun(1000);
System.out.println(fun);
}
public static int fun(int num) {
if (num > 0 && num < 5) {
return 0;
} else {
return num / 5 + fun(num / 5);
}
}
}
⑦约瑟夫环
public class Test8 {
/**
* 约瑟夫环
*
* @param args
*/
@SuppressWarnings("resource")
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Scanner sc = new Scanner(System.in);
System.out.println("请输入参加约瑟夫环的总数:");
int q = sc.nextInt();
for (int i = 1; i <= q; i++) {
list.add(i);
}
// System.out.println(list);
int count = 1;
for (int i = 0; list.size() != 1; i++) {
if (i == list.size()) {
i = 0;
}
if (count % 3 == 0) {
list.remove(i--);
}
count++;
}
System.out.println(list.get(0));
}
}