文章目录
IO流
相关资源
java IO流详解 - 时光孤岛 - 博客园 (cnblogs.com)
1. File
File类是java.io包下代表与平台无关的文件和目录,如果希望在程序中操作文件和目录,都可以通过File类来完成。
值得指出的是,不管是文件还是目录都是使用File来操作的,File能新建、删除、重命名文件和目录,File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
注意:
Windows 的路径分隔符使用反斜线(),而 Java程序中的反斜线表示转义字符,所以如果需要在 Windows的路径下包括反斜线,则应该使用两条反斜线,如F:\ \ abc \ \ test.txt,或者直接使用斜线(/ )也可以,Java程序支持将斜线当成平台无关的路径分隔符。
访问文件和目录
File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。
在默认情况下,系统总是依据用户的工作路径来解释相对路径,通常也就是运行Java虚拟机时所在的路径。
一旦创建了File对象后,就可以调用File对象的方法来访问,File类提供了很多方法来操作文件和目录,下面列出一些比较常用的方法。
1.访问文件名相关的方法
-
String getName():返回此File对象所表示的文件名或路径名(如果是路径,则返回最后一级子路径名)。
-
String getPath():返回此File对象所对应的路径名。
-
File getAbsoluteFile():返回此File对象的绝对路径。
-
String getAbsolutePath():返回此 File对象所对应的绝对路径名。
-
String getParent():返回此File对象所对应目录(最后一级子目录)的父目录名。
-
boolean renameTo(File newName):重命名此File对象所对应的文件或目录,如果重命名成功,则返回true;否则返回false。
2.文件检测相关的方法
- boolean exists():判断File对象所对应的文件或目录是否存在。
- boolean canWrite():判断File对象所对应的文件和目录是否可写。
- boolean canRead(:判断File对象所对应的文件和目录是否可读。
- boolean isFile():判断File对象所对应的是否是文件,而不是目录。
- boolean isDirectory():判断File对象所对应的是否是目录,而不是文件。
- boolean isAbsolute()😗*判断File对象所对应的文件或目录是否是绝对路径。**该方法消除了不同平台的差异,可以直接判断File对象是否为绝对路径。在 UNIX/Linux/BSD等系统上,如果路径名开头是一条斜线(/),则表明该File对象对应一个绝对路径;在Windows等系统上,如果路径开头是盘符,则说明它是一个绝对路径。
获取常规文件信息
- long lastModified():返回文件的最后修改时间。
- long length():返回文件内容的长度。
文件操作相关的方法
- boolean createNewFile():当此File对象所对应的文件不存在时,该方法将新建一个该File对象所指定的新文件,如果创建成功则返回 true;否则返回false。
- boolean delete():删除File对象所对应的文件或路径。
- **static File createTempFile(String prefix, String suffix)😗*在默认的临时文件目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名。**这是一个静态方法,可以直接通过File类来调用。prefix参数必须至少是3字节长。建议前缀使用一个短的、有意义的字符串,比如"hjb”或"mail"。suffix 参数可以为 null,在这种情况下,将使用默认的后缀“.tmp”。
- static File createTempFile(String prefix, String suffix, File directory):在directory 所指定的目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名。这是一个静态方法,可以直接通过File类来调用。(虚拟机退出后,删除文件)
- void deleteOnExit():注册一个删除钩子,指定当Java虚拟机退出时,删除 File对象所对应的文件和目录。
目录操作相关的方法
- **boolean mkdir():试图创建一个File对象所对应的目录,**如果创建成功,则返回true;否则返回false。调用该方法时File对象必须对应一个路径,而不是一个文件。
- **String[] list():列出 File对象的所有子文件名和路径名,**返回String 数组。
- File[ ] listFiles():列出 File对象的所有子文件和路径,返回File数组。
- static File[ ] listRoots():列出系统所有的根路径。这是一个静态方法,可以直接通过File类来调用。
相对路径和绝对路径
public class FileTest
{
public static void main(String[] args)
throws IOException
{
// 以当前路径来创建一个File对象
File file = new File(".");
// 直接获取文件名,输出一点
System.out.println(file.getName());
// 获取相对路径的父路径可能出错,下面代码输出null
System.out.println(file.getParent());
// 获取绝对路径
System.out.println(file.getAbsoluteFile());
// 获取上一级路径
System.out.println(file.getAbsoluteFile().getParent());
}
}
创建文件
注意:
- 同一个File对象,不能即创建文件夹,又创建文件
// 以当前路径来创建一个File对象
File file = new File(".");
System.out.println(file.createNewFile());//false
// 在当前路径下创建一个临时文件
File tmpFile = File.createTempFile("tempFile", ".txt", file); //file 表示在这个文件夹下
// 指定当JVM退出时删除该文件
tmpFile.deleteOnExit();
//创建一个文件
File newFile = new File(System.currentTimeMillis() + ""); //文件名称:当前时间戳
newFile.createNewFile(); 以指定newFile对象来创建一个文件
System.out.println("newFile对象是否存在:" + newFile.exists());
//创建文件夹
File newFile = new File(System.currentTimeMillis() + "");
newFile.createNewFile();
System.out.println("newFile对象是否存在:" + newFile.exists()); true
System.out.println(newFile.mkdir()); false // 因为newFile已创建文件。同一个文件对象不能即创建文件,又创建文件夹
newFile创建的文件
newFile创建的文件夹
查看文件列表
File file = new File(".");
String[] fileList = file.list();
System.out.println("====当前路径下所有文件和路径如下====");
for (String fileName : fileList)
{
System.out.println(fileName);
}
获取所有磁盘的根路径
File[] roots = File.listRoots();
System.out.println("====系统所有根路径如下====");
for (File root : roots)
{
System.out.println(root);
}
文件过滤器
在File类的 list()方法中可以接收一个FilenameFilter参数,通过该参数可以只列出符合条件的文件。
FilenameFilter接口里包含了一个accept(File dir, String name)方法,该方法将依次对指定File 的所有子目录或者文件进行迭代,如果该方法返回true,则 list()方法会列出该子目录或者文件。
@FunctionalInterface
public interface FilenameFilter {
/**
* Tests if a specified file should be included in a file list.
*
* @param dir the directory in which the file was found.
* @param name the name of the file.
* @return <code>true</code> if and only if the name should be
* included in the file list; <code>false</code> otherwise.
*/
boolean accept(File dir, String name);
}
实战:
// 以当前路径来创建一个File对象
File file = new File(".");
//过滤掉后缀名是Java的文件和所有文件夹
String[] list = file.list((dir, name) -> name.endsWith(".java") || new File(name).isDirectory());
Arrays.stream(list).forEach(System.out::println);
小结
- Windows 的路径分隔符使用反斜线( \ ),而Java程序中的反斜线表示转义字符,所以如果需要在Windows的路径下包括反斜线,则应该使用两条反斜线,如 F:\ \abc\ \test.txt,或者直接使用斜线(/)也可以,Java程序支持将斜线当成平台无关的路径分隔符.
- 同一个File对象,不能即创建文件夹,又创建文件
- 可以使用文件过滤器,来过滤不需要的文件
2. IO流
Java中把不同的输入/输出源(键盘、文件、网络连接等)抽象表述为“流”( stream),通过流的方式允许Java 程序使用相同的方式来访问不同的输入/输出源。stream是从起源(source)到接收(sink)的有序数据。
Java把所有传统的流类型(类或抽象类)都放在java.io包中,用以实现输入/输出功能。
需要掌握
流的分类
- 输入输出流
- 输入流:只能从中读取数据,而不能向其写入数据。 从硬盘到内存
- InputStream和Reader作为基类
- 输出流:只能向其写入数据,而不能从中读取数据。 从内存到硬盘
- OutputStream和Writer作为基类
- 输入流:只能从中读取数据,而不能向其写入数据。 从硬盘到内存
- 字符流和字节流
- 字符流:
- Reader和Writer作为基类
- 字节流:
- InputStream和OutputStream作为基类
- 字节流和字符流的用法几乎完全一样,区别:字节流和字符流所操作的数据单元不同,字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。
- 字符流:
- 节点流和处理流
- 按照流的角色可以分为以上两种
- 节点流:
- 可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被称为低级流
- 程序直接连接数据源,和实际的输入输出节点链接。
- 可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被称为低级流
- 处理流:
- 使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入/输出节点连接。
- 使用处理流的一个明显好处是,只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码来访问不同的数据源,随着处理流所包装节点流的变化,程序实际所访问的数据源也相应地发生变化。
- Java使用处理流来包装节点流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入/输出功能。因此处理流也被称为包装流。
- 使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入/输出节点连接。
流的概念模型
Java把所有设备里的有序数据抽象成流模型,简化了输入/输出处理。
Java 的IO流共涉及40 多个类,这些类都是从4个抽象基类派生的。
InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。
InputStream和 Reader,把输入设备抽象成一个“水管”,这个水管里的每个“水滴”依次排列
输入流:
字节流和字符流的处理方式其实非常相似,只是它们处理的输入/输出单位不同。
输入流使用隐式的记录指针来表示当前正准备从哪个“水滴”开始读取,每当程序从InputStream或 Reader里取出一个或多个“水滴”后,记录指针自动向后移动,InputStream和 Reader里都提供一些方法来控制记录指针的移动。
输出流
OutputStream和 Writer同样把输出设备抽象成一个“水管”,这个水管里没有任何水滴
当执行输出时,程序相当于依次把“水滴”放入到输出流的水管中,输出流同样采用隐式的记录指针来标识当前水滴即将放入的位置,每当程序向OutputStream或Writer里输出一个或多个水滴后,记录指针自动向后移动。
Java的处理流模型则体现了Java输入/输出流设计的灵活性。处理流的功能主要体现在以下两个方面
- 性能的提高:主要以增加缓冲的方式来提高输入/输出的效率。
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入/输出大批量的内容,而不是输入/输出一个或多个“水滴”。
处理流
通过使用处理流,Java程序无须理会输入/输出节点是磁盘、网络还是其他的输入/输出设备,程序只要将这些节点流包装成处理流,就可以使用相同的输入/输出代码来读写不同的输入/输出设备的数据。
3. 字节流和字符流
字节流和字符流的用法几乎完全一样,区别:字节流和字符流所操作的数据单元不同,字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。
windows系统中,一个系统占一个字节,一个中文字符占两个字节
Java中,一个字母占两个字节,一个中文数字占两个字节。
使用字节流读取汉字’中’时,第一次读中的一半,第二次读中的下一半
Input和Reader(重点)
InputStream的方法。
- int read():从输入流中读取单个字节(相当于从水管中取出一滴水),返回所读取的字节数据(字节数据可直接转换为int类型)。
- int read(byte[ ] b):从输入流中最多读取 b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
- int read(byte[] b, int off, int len):从输入流中最多读取len个字节的数据,并将其存储在数组 b中,放入数组b中时,并不是从数组起点开始,而是从 off位置开始,返回实际读取的字节数。在 Reader里包含如下三个方法。
Reader的方法。
- int read():从输入流中读取单个字符(相当于从水管中取出一滴水),返回所读取的字符数据(字符数据可直接转换为int类型)。
- int read(char[]cbuf):从输入流中最多读取cbuf.length个字符的数据,并将其存储在字符数组cbuf中,返回实际读取的字符数。
- int read(char[] cbuf, int off, int len):从输入流中最多读取len个字符的数据,并将其存储在字符数组 cbuf中,放入数组cbuf中时,并不是从数组起点开始,而是从 off位置开始,返回实际读取的字符数。
移动指针的方法
- void mark(int readAheadLimit):在记录指针当前位置记录一个标记(mark)。
- boolean markSupported():判断此输入流是否支持mark()操作,即是否支持记录标记。void
- reset():将此流的记录指针重新定位到上一次记录标记(mark)的位置。
- long skip(long n):记录指针向前移动n个字节/字符。
当read方法返回-1时,到了输入流的结束点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GeRTQE10-1639295989511)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20211201212634383.png)]
InputStream和 Reader都是抽象类,本身不能创建实例,它们分别有一个用于读取文件的输入流: FileInputStream和 FileReader,它们都是节点流——会直接和指定文件关联。
Input读取当前文件
一次读取一个字节
public class FileInputStreamTest
{
public static void main(String[] args) throws IOException
{
FileInputStream fileInputStream = new FileInputStream("src/lession_IO/test.txt");
System.out.println(fileInputStream.read());// a 97
System.out.println(fileInputStream.read());// a 98
System.out.println(fileInputStream.read());// a 99
System.out.println(fileInputStream.read());
System.out.println(fileInputStream.read());
System.out.println(fileInputStream.read());
System.out.println(fileInputStream.read());
System.out.println(fileInputStream.read());// -1
}
一次读取多个字节
public class FileInputStreamTest
{
public static void main(String[] args) throws IOException
{
// 创建字节输入流
InputStream fis = new FileInputStream(
"src/lession_IO/FileInputStreamTest.java");
// 创建一个长度为1024的“竹筒”
byte[] bbuf = new byte[1024];
// 用于保存实际读取的字节数
int hasRead = 0;
// 使用循环来重复“取水”过程
while ((hasRead = fis.read(bbuf)) > 0 )
{
// 取出“竹筒”中水滴(字节),将字节数组转换成字符串输入!
System.out.print(new String(bbuf , 0 , hasRead ));
}
// 关闭文件输入流,放在finally块里更安全
fis.close();
}
}
注意:
- idea和eclipse的当前路径指的是当先项目的工程目录,并不是当前文件所在的目录。
- 如果使用黑窗口运行,路径直接是“FileInputStreamTest.java”
- 使用了流之后要fis.close()来关闭该文件输入流,与JDBC编程一样,程序里打开的文件IO资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源。Java 7改写了所有的IO资源类,它们都实现了AutoCloseable接口,都可通过自动关闭资源的try语句来关闭这些IO流。
Reader读取当前文件
public class FileReaderTest {
public static void main(String[] args) {
try (
// 创建字符输入流
Reader fr = new FileReader("src/lession_IO/FileReaderTest.java")) {
// 创建一个长度为32的“竹筒”
char[] cbuf = new char[32];
// 用于保存实际读取的字符数
int hasRead = 0;
// 使用循环来重复“取水”过程
while ((hasRead = fr.read(cbuf)) > 0) {
// 取出“竹筒”中水滴(字符),将字符数组转换成字符串输入!
System.out.print(new String(cbuf, 0, hasRead));
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Output和Writer(重点)
Output的方法
- void write(int c):将指定的字节/字符输出到输出流中,其中c既可以代表字节,也可以代表字
符。 - void write(byte[]/char[] buf):将字节数组/字符数组中的数据输出到指定输出流中。
- void write(byte[]/char[] buf, int off, int len):将字节数组/字符数组中从off位置开始,长度为len
的字节/字符输出到输出流中。
因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String 对象作为参数。Writer里还包含如下两个方法。
Writer的方法
-
void write(int c):将指定的字节/字符输出到输出流中,其中c既可以代表字节,也可以代表字
符。 -
void write(byte[]/char[] buf):将字节数组/字符数组中的数据输出到指定输出流中。
-
void write(byte[]/char[] buf, int off, int len):将字节数组/字符数组中从off位置开始,长度为len
的字节/字符输出到输出流中。 -
void write(String str):将str字符串里包含的字符输出到指定输出流中。
-
void write(String str, int off, int len):将str字符串里从 off位置开始,长度为len的字符输出到指定输出流中。
注意:写的时候可以以字符串往流中写,但是读的时候不能以字符串的形式读。
文件复制
使用InputStream读,读之后使用OutputStream写
public class FileOutputStreamTest
{
public static void main(String[] args)
{
try(
// 创建字节输入流
FileInputStream fis = new FileInputStream(
"src/lession_IO/FileOutputStreamTest.java");
// 创建字节输出流
FileOutputStream fos = new FileOutputStream("src/FileOutputStreamTest.txt"))
{
byte[] bbuf = new byte[32];
int hasRead = 0;
// 循环从输入流中取出数据
while ((hasRead = fis.read(bbuf)) > 0 )
{
// 每读取一次,即写入文件输出流,读了多少,就写多少。
fos.write(bbuf , 0 , hasRead);
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
使用Writer输出字符串
public class FileWriterTest
{
public static void main(String[] args)
{
try(
FileWriter fw = new FileWriter("src/锦瑟.txt"))
{
fw.write("锦瑟 - 李商隐\r\n");
fw.write("锦瑟无端五十弦,一弦一柱思华年。\r\n");
fw.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃。\r\n");
fw.write("沧海月明珠有泪,蓝田日暖玉生烟。\r\n");
fw.write("此情可待成追忆,只是当时已惘然。\r\n");
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
注意:
- 字符串内容的最后是\rln,这是Windows平台的换行符,通过这种方式就可以让输出内容换行;如果是UNIX/Linux/BSD等平台,则使用n就作为换行符。
课堂练习
字节读写
public class FileOutputStreamTest01 {
public static void main(String[] args) throws IOException {
//字节写
// OutputStream outputStream = new FileOutputStream("test01.txt");
// outputStream.write(97);
// byte[] outputByte = {96, 97, 97, 98};
// outputStream.write(outputByte);
// outputStream.flush();
// outputStream.close();
//字节读
InputStream inputStream = new FileInputStream("test01.txt");
System.out.println(inputStream.read());
byte[] inputByte = new byte[3];
int a = 0;
while ((a = inputStream.read(inputByte)) > 0) {
System.out.println(new String(inputByte,0,a));
}
inputStream.close();
}
}
字符读写
public class FileReaderTest01 {
public static void main(String[] args) throws IOException {
//字符写
// Writer writer = new FileWriter("test02.txt");
// writer.write(97);
// char[] writerChar = {96, 97, 98, 99, 100};
// writer.write(writerChar);
// writer.flush();
// writer.close();
//字符读
Reader reader = new FileReader("test02.txt");
System.out.println((char) reader.read());
char[] readerChar = new char[3];
int a = 0;
while ((a = reader.read(readerChar)) > 0) {
System.out.println(new String(readerChar, 0, a));
}
reader.close();
}
}
close和flush(重点)
所有的流都实现了java .io.closeable接口,都是可关闭的,都有ciose()方法。流是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费占用很多资源。用完流一定要关闭。
所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法。输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。
注意:如果没有flush()可能会导致丢失数据。
带有缓冲区的字符流
- 使用这个流不需要自定义数组,或者不用自定义byte数组,自带缓冲。
- 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流
- 外部负责包装的流,叫做包装流,又叫处理流。
- 处理流只关闭自己即可,不需要关闭他包装的节点流,源代码中已自动关闭
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close();
} finally {
in = null;
cb = null;
}
}
}
- br.readLine()一次读取一行数据,没有换行符
- BufferedWriter的newLine()提供了一个换行操作
- newLine()支持跨平台(因为IO操作时,操作的是当前的系统文件,与Java平台无关,不同平台的文件的换行符不同。\n在window系统中表示换行,mac就不一定)
读操作
public class FileReaderTesst {
public static void main(String[] args) throws IOException {
//原来写法
FileReader fileReader1 = new FileReader("src/lession_IO/FileReaderTesst.java");
char [] chars = new char[50];
int a =0;
while ((a=fileReader1.read(chars))>0){
System.out.println(new String(chars,0,chars.length));
System.out.println("---------------进行了读取操作----------------");
}
fileReader1.close();
System.out.println("------------------------------使用处理流------------------------------------------");
//使用处理流/包装流处理
FileReader fileReader2 = new FileReader("src/lession_IO/FileReaderTesst.java");
BufferedReader bufferedReader = new BufferedReader(fileReader2);
//读取一行数据
// String line = bufferedReader.readLine();
// System.out.println(line);
//读取全篇
String line = null;
while ((line=bufferedReader.readLine())!=null){
System.out.println(line);
System.out.println("---------------进行了读取操作----------------");
}
bufferedReader.close();
}
}
写操作
public class BufferReaderTest01 {
public static void main(String[] args) throws IOException {
//使用包装流读取字符串
// Reader reader = new FileReader("test02.txt");
// BufferedReader bufferedReader = new BufferedReader(reader);
// String line = null;
// while ((line = bufferedReader.readLine()) != null) {
// System.out.println(line);
// }
// bufferedReader.close();
//使用包装流写字符串
Writer writer = new FileWriter("test03.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer);
bufferedWriter.write("春晓春眠不觉晓⑴,");
bufferedWriter.newLine();
bufferedWriter.write("处处闻啼鸟⑵。");
bufferedWriter.newLine();
bufferedWriter.write("夜来风雨声⑶,");
bufferedWriter.newLine();
bufferedWriter.write("花落知多少⑷。");
bufferedWriter.flush();
bufferedWriter.close();
}
}
将字节流转换成字符流
问题:
我们使用BufferReader读取字节流
发现不能直接转换。
我们可以使用InputStreamReader将字节流转换成字符流。
输入流
public class FileReaderTest02 {
public static void main(String[] args) throws IOException {
//字节流
FileInputStream fileInputStream = new FileInputStream("src/lession_IO/FileReaderTest02.java");
//使用转换流将字节流转换成字符流 fileInputStream相当于节点流,inputStreamReader相当于包装流
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
//将字符流转换成带有缓冲区的字符流 inputStreamReader相当于节点流,bufferedReader相当于包装流
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = null;
while ((line=bufferedReader.readLine())!=null){
System.out.println(line);
System.out.println("-----------------进行了输出-----------------");
}
}
}
将上述代码合并。
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("src/lession_IO/FileReaderTest02.java")));
String line = null;
while ((line=bufferedReader.readLine())!=null){
System.out.println(line);
System.out.println("-----------------进行了输出-----------------");
}
输出流
public class FileWriterTest01 {
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("src/lession_IO/小情歌.txt",true);//true表示在源文件中追加,可写可不写
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
bufferedWriter.write("这是一首简单的小情歌\n");
bufferedWriter.write("记录着我们的快乐");
bufferedWriter.flush();
bufferedWriter.close();
}
}
字节-字符-缓冲对比
public class InputSteamReaderTest {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("test03.txt");
//使用字节流读取
// System.out.println(fileInputStream.read());
// byte[] streamArr = new byte[10];
// int streamNum=0;
// while ((streamNum=fileInputStream.read(streamArr))>0){
// System.out.println(new String(streamArr,0,streamNum));
// System.out.println("----------读取数据---------");
// }
// fileInputStream.close();
//将字节流包装成字符流进行输出
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
// char[] streamReader = new char[10];
// int streamReaderNum = 0;
// while ((streamReaderNum = inputStreamReader.read(streamReader)) > 0) {
// System.out.println(new String(streamReader, 0, streamReaderNum));
// System.out.println("----------读取数据---------");
// }
//将字符流包装成缓冲流进行输出
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line =null;
while ((line=bufferedReader.readLine())!=null){
System.out.println(line);
System.out.println("----------读取数据---------");
}
//套娃写法
BufferedReader bufferedReader1 = new BufferedReader(new InputStreamReader(new FileInputStream("test03.txt")));
}
}
4. 数据流
DataInputStream
DataOutputStream
- 使用DataOutputStream处理的文件,使用我们的记事本是打不开的,我们要想获取数据,必须使用DataInputStream并且使用对应的规则进行读取。
- DataOutputStream向文件中输出内容时,会将内容的类型一并传送到文件中
public class DataInputStreamTest {
public static void main(String[] args) throws IOException {
//写文件
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("data"));
dataOutputStream.write(1);
dataOutputStream.writeDouble(3.14);
dataOutputStream.writeBoolean(false);
dataOutputStream.flush();
dataOutputStream.close();
//读文件
DataInputStream dataInputStream = new DataInputStream(new FileInputStream("data"));
System.out.println(dataInputStream.read());
System.out.println(dataInputStream.readDouble());
System.out.println(dataInputStream.readBoolean());
}
}
课堂练习
public class DataOutPutStreamTest {
public static void main(String[] args) throws IOException {
//写操作
// OutputStream fileOutputStream = new FileOutputStream("dataFile.txt");
// DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
// dataOutputStream.writeDouble(3.14);
// dataOutputStream.writeByte(1);
// dataOutputStream.writeShort(2);
// dataOutputStream.writeBoolean(true);
// dataOutputStream.writeLong(1555555L);
// dataOutputStream.flush();
// dataOutputStream.close();
//读操作
InputStream fileInputStream = new FileInputStream("dataFile.txt");
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
//如果不按照顺序去读,读出来的数据和写入的数据不一致。
// System.out.println(dataInputStream.readBoolean());
// System.out.println(dataInputStream.readDouble());
// System.out.println(dataInputStream.readLong());
//按照顺序去读,数据正常显示。
System.out.println(dataInputStream.readDouble());
System.out.println(dataInputStream.readByte());
System.out.println(dataInputStream.readShort());
System.out.println(dataInputStream.readBoolean());
System.out.println(dataInputStream.readLong());
}
}
5. 标准输出流
使用PrintlnStream
正常情况下使用System.out.println() 将信息输出到系统中。通过PrintlnStream我们可以使用System.out.println()将信息输出到外部文件中
public class PrintlnStreamTest {
public static void main(String[] args) throws IOException {
PrintStream printStream = System.out;
printStream.println("春眠不觉晓");
printStream.println("处处闻啼鸟");
printStream.println("夜来风雨声");
printStream.println("花落知多少");
PrintStream printlnStream = new PrintStream(new FileOutputStream("log.txt"));
System.setOut(printlnStream);//更改信息的输出方向,将信息输出到文件
System.out.println("春眠不觉晓");
System.out.println("处处闻啼鸟");
printlnStream.println("夜来风雨声");
printlnStream.println("花落知多少");
}
}
实战:日志记录文件
class LogUtils {
public static void log(String message) throws IOException {
PrintStream printlnStream = new PrintStream(new FileOutputStream("log.txt",true));
System.setOut(printlnStream);
System.out.println("时间:" + LocalDateTime.now() + ",输出日志:" + message);
}
}
public class PrintlnStreamTest {
public static void main(String[] args) throws IOException {
LogUtils.log("启动系统");
LogUtils.log("获取用户信息");
LogUtils.log("删除用户信息");
LogUtils.log("退出系统");
}
}
6. 对象专属流
Java中提供了ObjectInputStream和ObjectOutputStream来进行对象的序列化操作。即通过二者进行内存与磁盘Java对象的存储和读取。要进行序列化的类要实现Serializable接口,序列化这个类,如果类中的某个类不想序列化,可以使用 transient 修饰某个属性。如: private transient String name;
序列化与反序列化
理解
我们内存中Java对象,往硬盘中存储时,因为我们的对象太大,我们需要将Java对象拆分成一个个小的数据包,传到硬盘中。这是一个序列化的过程。
我们从硬盘中读取一个对象到Java程序中,此时也是通过一个个小小的数据包传输的,最后传输到内存中,这是一个反序列化的过程。
原理
序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]
数组。
为什么要把Java对象序列化呢?因为序列化后可以把byte[]
保存到文件中,或者把byte[]
通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。
有序列化,就有反序列化,即把一个二进制内容(也就是byte[]
数组)变回Java对象。有了反序列化,保存到文件中的byte[]
数组又可以“变回”Java对象,或者从网络上读取byte[]
并把它“变回”Java对象。
使用对象专属流
存储一个Java对象
class Employee implements Serializable {
private Long id;
private String name;
private LocalDate dob;
public Employee(Long id, String name, LocalDate dob) {
super();
this.id = id;
this.name = name;
this.dob = dob;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", dob=" + dob + "]";
}
}
public class ObjectOutputStreamTest {
public static void main(String[] args) throws IOException {
//存储对象
Employee employee = new Employee(1L, "张三", LocalDate.now());
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("students"));
oss.writeObject(employee);
oss.flush();
oss.close();
//获取对象
ObjectInputStream iss = new ObjectInputStream(new FileInputStream("students.txt"));
// Employee readEmployee = null;
// if ((readEmployee = (Employee) iss.readObject()) instanceof Employee) {
// System.out.println(readEmployee);
// }
Employee readEmployee = (Employee) iss.readObject();
System.out.println(readEmployee);
iss.close();
}
}
}
存储一个集合
public class ObjectOutputStreamTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//存储一个对象集合
Employee employee1 = new Employee(1L, "张三", LocalDate.now());
Employee employee2 = new Employee(1L, "张三", LocalDate.now());
Employee employee3 = new Employee(1L, "张三", LocalDate.now());
Employee employee4 = new Employee(1L, "张三", LocalDate.now());
List<Employee> employeeList = new ArrayList<>();
employeeList.add(employee1);
employeeList.add(employee2);
employeeList.add(employee3);
employeeList.add(employee4);
ObjectOutputStream outputStream1 = new ObjectOutputStream(new FileOutputStream("employeeList"));
outputStream1.writeObject(employeeList);
outputStream1.flush();
outputStream1.close();
//读取一个对象集合
ObjectInputStream inputStream1 = new ObjectInputStream(new FileInputStream("employeeList"));
List<Employee> list = (List<Employee>) inputStream1.readObject();
inputStream1.close();
System.out.println(list);
}
}
课堂练习
class Employee implements Serializable {
private static final long serialVersionUID = -7330558508657944084L;
private Long id;
private transient String name;
private LocalDate dob;
public Employee(Long id, String name, LocalDate dob) {
this.id = id;
this.name = name;
this.dob = dob;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", dob=" + dob + "]";
}
}
public class ObjectOutPutStreamTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//写操作
// Employee employee1 = new Employee(1L, "占山", LocalDate.now());
// Employee employee2 = new Employee(2L, "李四", LocalDate.now().plusDays(1));
// OutputStream fileOutputStream = new FileOutputStream("objectFile.txt");
// ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
// 一次写一个
// outputStream.writeObject(employee1);
// outputStream.writeObject(employee2);
// 一次写多个
// List<Employee> list = new ArrayList<>();
// for (long i = 0; i < 100; i++) {
// list.add(new Employee(i, "张" + i + "鸣", LocalDate.now().plusDays(i)));
// }
// outputStream.writeObject(list);
// outputStream.flush();
// outputStream.close();
//读操作
InputStream inputStream = new FileInputStream("objectFile.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
// Employee employee1 = (Employee)objectInputStream.readObject();
// Employee employee2 = (Employee)objectInputStream.readObject();
// System.out.println(employee1);
// System.out.println(employee2);
List<Employee> employees = (List<Employee>) objectInputStream.readObject();
System.out.println(employees);
}
}
Java对象序列化的注意
- 使用ObjectInputStream或ObjectOutputStream进行对象输入和输出的时候,我们需要将对象所在的类实现序列化接口Serializable
- 序列化接口中没有任何方法,属于Java程序的一个标记,有标识作用。Java虚拟机看到这个以及这类的标记接口,会进行特殊处理。
- Serializable接口会自动为我们当前类生成一个序列化版本号,序列化版本号分为可变与固定两种
- 可变的版本号:代码中我们不手动写版本号,运行程序时自动生成(我们修改程序后,相应的版本号会发生变化)
- 固定的版本号:对象的属性修改好,对象的版本号不会发生变化。
- 序列号的作用:Java语言区分类的机制
- Java语言区分类的时候,首先会通过类型进行对比,如果不一样,肯定不是同一个类
- 在类名一定的情况下,靠序列化版本号进行区分
- 序列化号的缺陷:
- 当我们使用ObjectOutputStream向磁盘保存一个Employee对象时,过了一段时间,我们修改了Employee的属性值,如果我们使用的是自动生成的序列化版本号,修改过后版本号是会发生改变的,此时我们InputOutputStream读取时就会发生错误。
- 向redis中存储数据时,如果存储时的对象和取出的对象有属性发生改变,此时会产生错误。
- 类实现序列化时,我们最好使用编辑器为我们生成的固定的序列化号。
idea生成序列化号
7. properties和IO的联合使用
我们可以将一些经常需要手动修改的数据,存储到我们的文件中,这样当需要我们修改这些文件时,我们就不需要停止系统,修改后再启动系统。而是直接修改文件,用程序读取即可。
这类文件成为配置文件,后缀名通常以.properties进行修饰。
使用properties和FileWriter是心啊读取用户帐号密码的操作。
public class PropertiesTest01 {
public static void main(String[] args) throws IOException {
Writer writer = new FileWriter("userInfo.txt");
writer.write("username=usernameOne;\n");
writer.write("password=passOne;");
writer.flush();
writer.close();
Reader reader = new FileReader("userInfo.txt");
Properties properties = new Properties();
properties.load(reader);
reader.close();
System.out.println(properties.get("username"));
System.out.println(properties.get("password"));
properties.setProperty("username", "usernameTwo");
properties.setProperty("password", "passTwo");
System.out.println(properties.get("username"));
System.out.println(properties.get("password"));
}
}
课堂练习
public class PropertiesTest {
public static void main(String[] args) throws IOException {
//写文件
// Properties properties = new Properties();
// OutputStream outputStream = new FileOutputStream("propertiesFile");
// properties.setProperty("username","148524167751140@wq.com");
// properties.setProperty("password","14577451");
// properties.setProperty("name","111");
// properties.setProperty("gender","man");
// //将文件写入磁盘
// properties.store(outputStream,"proFile");
//读文件
Properties properties = new Properties();
InputStream inputStream = new FileInputStream("propertiesFile");
properties.load(inputStream);
System.out.println(properties.get("username"));
System.out.println(properties.get("password"));
}
}
8. 推回输出流(了解)
在输入/输出流体系中,有两个特殊的流与众不同,就是PushbackInputStream和PushbackReader,
- void unread(byte[]J/char[] buf):将一个字节/字符数组内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
- void unread(byte[J/char[] b, int off, int len):将一个字节/字符数组里从off开始,长度为len字节/字符的内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
- void unread(int b):将一个字节/字符推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
这三个方法与read()方法一一对应。
这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread()方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()所需的数组时才会从原输入流中读取。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TCMRBs40-1639295989523)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20211201224940203.png)]
当程序创建一个 PushbackInputStream和PushbackReader 时需要指定推回缓冲区的大小,默认的推回缓冲区的长度为1。如果程序中推回到推回缓冲区的内容超出了推回缓冲区的大小,将会引发Pushback buffer overflow的IOException异常。
public class PushbackTest
{
public static void main(String[] args)
{
try(
// 创建一个PushbackReader对象,指定推回缓冲区的长度为64
PushbackReader pr = new PushbackReader(new FileReader(
"src/lession_IO/PushbackTest.java") , 64))
{
char[] buf = new char[32];
// 用以保存上次读取的字符串内容
String lastContent = "";
int hasRead = 0;
// 循环读取文件内容
while ((hasRead = pr.read(buf)) > 0)
{
// 将读取的内容转换成字符串
String content = new String(buf , 0 , hasRead);
int targetIndex = 0;
// 将上次读取的字符串和本次读取的字符串拼起来,
// 查看是否包含目标字符串, 如果包含目标字符串
if ((targetIndex = (lastContent + content)
.indexOf("new PushbackReader")) > 0)
{
// 将本次内容和上次内容一起推回缓冲区
pr.unread((lastContent + content).toCharArray());
// 重新定义一个长度为targetIndex的char数组
if(targetIndex > 32)
{
buf = new char[targetIndex];
}
// 再次读取指定长度的内容(就是目标字符串之前的内容)
pr.read(buf , 0 , targetIndex);
// 打印读取的内容
System.out.print(new String(buf , 0 ,targetIndex));
System.exit(0);
}
else
{
// 打印上次读取的内容
System.out.print(lastContent);
// 将本次内容设为上次读取的内容
lastContent = content;
}
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
9. RandomAccessFile(了解)
既可以读取文件,又可以向文件输出数据。
与普通输出流不同的是,支持随机访问,可以跳转到文件的任意地方修改数据。
由于RandomAccessFile可以自由访问文件的任意位置,所以如果只需要访问文件部分内容,而不是把文件从头读到尾,使用RandomAccessFile将是更好的选择。
与OutputStream、Writer 等输出流不同的是,RandomAccessFile 允许自由定位文件记录指针,RandomAccessFile可以不从开始的地方开始输出,因此 RandomAccessFile可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用RandomAccessFile。
RandomAccessFile的方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他 IO节点。
RandomAccessFile对象包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个RandomAccessFile对象时,该对象的文件记录指针位于文件头(也就是0处),当读/写了n个字节后,文件记录指针将会向后移动n个字节。除此之外,RandomAccessFile可以自由移动该记录指针,既可以向前移动,也可以向后移动。RandomAccessFile包含了如下两个方法来操作文件记录指针。
-
long getFilePointer():返回文件记录指针的当前位置。
-
void seek(long pos):将文件记录指针定位到pos位置。
RandomAccessFile既可以读文件,也可以写,所以它既包含了完全类似于InputStream的三个read()方法,其用法和InputStream的三个read()方法完全一样;也包含了完全类似于OutputStream的三个write()方法,其用法和OutputStream的三个write()方法完全一样。
除此之外,RandomAccessFile 还包含了一系列的readXxx()和 writeXxx()方法来完成输入、输出。
RandomAccessFile类有两个构造器,其实这两个构造器基本相同,只是指定文件的形式不同而已一个使用String参数来指定文件名,一个使用File参数来指定文件本身。除此之外,创建RandomAccessFile对象时还需要指定一个mode参数,该参数指定RandomAccessFile 的访问模式,该参数有如下4个值。
“r”:以只读方式打开指定文件。如果试图对该RandomAccessFile执行写入方法,都将抛出IOException异常。
“rw”:以读、写方式打开指定文件。如果该文件尚不存在,则尝试创建该文件。
“rws”:以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
“rwd”:以读、写方式打开指定文件。相对于"rw"模式,还要求对文件内容的每个更新都同步写入到底层存储设备。
读操作
public class RandomAccessFileTest
{
public static void main(String[] args)
{
try(
RandomAccessFile raf = new RandomAccessFile(
"src/lession_IO/RandomAccessFileTest.java" , "r"))
{
// 获取RandomAccessFile对象文件指针的位置,初始位置是0
System.out.println("RandomAccessFile的文件指针的初始位置:"
+ raf.getFilePointer());
// 移动raf的文件记录指针的位置
raf.seek(300);
byte[] bbuf = new byte[1024];
// 用于保存实际读取的字节数
int hasRead = 0;
// 使用循环来重复“取水”过程
while ((hasRead = raf.read(bbuf)) > 0 )
{
// 取出“竹筒”中水滴(字节),将字节数组转换成字符串输入!
System.out.print(new String(bbuf , 0 , hasRead ));
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
写操作
public class AppendContent
{
public static void main(String[] args)
{
try(
//以读、写方式打开一个RandomAccessFile对象
RandomAccessFile raf = new RandomAccessFile("out.txt" , "rw"))
{
//将记录指针移动到out.txt文件的最后
raf.seek(raf.length());
raf.write("追加的内容!\r\n".getBytes());
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
注意:
RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输出的内容会覆盖文件中原有的内容。如果需要向指定位置插入内容,程序需要先把插入点后面的内容读入缓冲区,等把需要插入的数据写入文件后,再将缓冲区的内容追加到文件后面。
public class InsertContent
{
public static void insert(String fileName ,long pos
, String insertContent) throws IOException
{
File tmp = File.createTempFile("tmp" , null);
tmp.deleteOnExit();
try(
RandomAccessFile raf = new RandomAccessFile(fileName , "rw");
// 使用临时文件来保存插入点后的数据
FileOutputStream tmpOut = new FileOutputStream(tmp);
FileInputStream tmpIn = new FileInputStream(tmp))
{
raf.seek(pos);
// ------下面代码将插入点后的内容读入临时文件中保存------
byte[] bbuf = new byte[64];
// 用于保存实际读取的字节数
int hasRead = 0;
// 使用循环方式读取插入点后的数据
while ((hasRead = raf.read(bbuf)) > 0 )
{
// 将读取的数据写入临时文件
tmpOut.write(bbuf , 0 , hasRead);
}
// ----------下面代码插入内容----------
// 把文件记录指针重新定位到pos位置
raf.seek(pos);
// 追加需要插入的内容
raf.write(insertContent.getBytes());
// 追加临时文件中的内容
while ((hasRead = tmpIn.read(bbuf)) > 0 )
{
raf.write(bbuf , 0 , hasRead);
}
}
}
public static void main(String[] args)
throws IOException
{
insert("src/lession_IO/InsertContent.java" , 45 , "插入的内容\r\n");
}
}
上面程序中使用File的create’ TempFile(String prefix, String suffix)方法创建了一个临时文件(该临时文件将在JVM退出时被删除),用以保存被插入文件的插入点后面的内容。程序先将文件中插入点后的内容读入临时文件中,然后重新定位到插入点,将需要插入的内容添加到文件后面,最后将临时文件的内容添加到文件后面,通过这个过程就可以向指定文件、指定位置插入内容。每次运行上面程序,都会看到向InsertContent.java 中插入了一行字符串。
10. 练习
制作一个控制台购物商城管理系统,信息存储和用户的登陆使用IO实现。
- 登录用户
- 登录用户
- 用户的帐号信息(用户名,密码)等在userInfo.txt文件中存储。
- 不可登录文件中没有的账户信息
- 商品信息(id是商品的唯一标识,商品的id不能有重复)
- 查看商品(列出所有商品)(商品分为手机和书本)
- 添加商品信息(添加到磁盘文件shop.txt中)注意:商品id不能重复
- 删除商品信息(删除磁盘文件shop.txt中对应的商品信息)注意:不能删除无的商品信息
- 修改商品信息(修改磁盘文件shop.txt中对应的商品信息)注意:不能修改不存在的商品信息
- 退出系统
用户属性
private String userName;
private String passWord;
书本属性
private Integer id;
private String name;
private Integer type;
private BigDecimal price;
private Integer pageNum;
private String author;
手机属性
private Integer id;
private String name;
private Integer type;
private BigDecimal price;
private Integer cameraNum;
代码
书籍
public class Book extends ShopMessage implements Serializable {
private Integer pageNum;
private String author;
public Book(Integer id, String name, Integer type, BigDecimal price, Integer pageNum, String author) {
super(id, name, type, price);
this.pageNum = pageNum;
this.author = author;
}
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "Book{" +
"id=" + super.getId() +
", name='" + super.getName() + '\'' +
", type='" + super.getType() + '\'' +
", price=" + super.getPrice() +
",pageNum=" + pageNum +
", author='" + author + '\'' +
'}';
}
}
手机
public class Phone extends ShopMessage implements Serializable {
private Integer cameraNum;
public Phone(Integer id, String name, Integer type, BigDecimal price, Integer cameraNum
) {
super(id, name, type, price);
this.cameraNum = cameraNum;
}
public Integer getCameraNum() {
return cameraNum;
}
public void setCameraNum(Integer cameraNum) {
this.cameraNum = cameraNum;
}
@Override
public String toString() {
return "Phone{" +
"id=" + super.getId() +
", name='" + super.getName() + '\'' +
", type='" + super.getType() + '\'' +
", price=" + super.getPrice() +
",cameraNum=" + cameraNum +
'}';
}
}
商品父类
public class ShopMessage implements Serializable {
private Integer id;
private String name;
private Integer type;
private BigDecimal price;
public ShopMessage(Integer id, String name, Integer type, BigDecimal price) {
this.id = id;
this.name = name;
this.type = type;
this.price = price;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ShopMessage that = (ShopMessage) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public String toString() {
return "ShopMessage{" +
"id=" + id +
", name='" + name + '\'' +
", type='" + type + '\'' +
", price=" + price +
'}';
}
}
枚举类
public enum ShopType {
BOOK_TYPE(1, "书籍"),
PHONE_TYPE(2, "智能手机");
private int typeId;
private String typeName;
ShopType(int typeId, String typeName) {
this.typeId = typeId;
this.typeName = typeName;
}
public int getTypeId() {
return typeId;
}
public String getTypeName() {
return typeName;
}
}
用户类
public class User implements Serializable {
private String userName;
private String passWord;
public User(String username, String password) {
this.userName=username;
this.passWord=password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (!userName.equals(user.userName)) return false;
return passWord.equals(user.passWord);
}
@Override
public int hashCode() {
int result = userName.hashCode();
result = 31 * result + passWord.hashCode();
return result;
}
}
购物系统
public class ShopSystem {
private static final ShopSystem shopSystem = new ShopSystem();
private ShopSystem(){};
public static void main(String[] args) throws Exception {
System.out.println("请登录阿里99无人售货店");
boolean islogin = false;
do {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入帐号");
String username = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
User user = new User(username, password);
islogin = ShopSystem.loginSystem(user);
if (!islogin) System.out.println("账号或密码错误,请重新输入");
} while (!islogin);
}
private static boolean loginSystem(User user) throws IOException, ClassNotFoundException {
Properties properties = new Properties();
properties.load(new FileReader("src/zong_he_test/user.properties"));
String localUsername = properties.getProperty("username");
String localPassword = properties.getProperty("password");
User localUser = new User(localUsername, localPassword);
if (user != null && localUser != null && localUser.equals(user)) {
System.out.println("欢迎来到阿里99无人售货店");
ShopSystem.choseFunction();
return true;
} else {
System.out.println("密码不正确,请重新输入");
return false;
}
}
private static void choseFunction() {
while (true) {
System.out.println("请选择输入您要进行的操作:\n" +
"1. 查看所有商品 \n" +
"2. 添加商品 \n" +
"3. 删除商品 \n" +
"4. 修改商品 \n" +
"5. 任意键退出系统"
);
Scanner scanner = new Scanner(System.in);
int fun = scanner.nextInt();
switch (fun) {
//查看所有商品
case 1:
shopSystem.getAllShopMessages();
break;
//添加商品
case 2:
shopSystem.addShopMessage();
break;
//删除商品
case 3:
shopSystem.deleteShopMessage();
break;
//修改商品
case 4:
shopSystem.editShopMessage();
break;
default:
System.out.println("退出系统");
System.exit(0);
break;
}
}
}
private void editShopMessage() {
List<ShopMessage> allShopMessages = getAllShopMessages();
System.out.println("请选择需要修改商品的id");
Scanner scanner = new Scanner(System.in);
int id = scanner.nextInt();
boolean isSuccess = false;
for (int i = 0; i < allShopMessages.size(); i++) {
if (allShopMessages.get(i).getId().equals(id)) {
ShopMessage shopMessage = allShopMessages.get(i);
if (shopMessage.getType().equals(ShopType.PHONE_TYPE.getTypeId())) {
System.out.println("请输入手机名称");
String name = scanner.next();
System.out.println("请输入价格");
BigDecimal price = scanner.nextBigDecimal();
System.out.println("请输入像素");
Integer cameraNum = scanner.nextInt();
allShopMessages.set(i, new Phone(shopMessage.getId(), name, shopMessage.getType(), price, cameraNum));
isSuccess = true;
} else if (shopMessage.getType().equals(ShopType.BOOK_TYPE.getTypeId())) {
System.out.println("请输入书名");
String name = scanner.next();
System.out.println("请输入作者名称");
String author = scanner.next();
System.out.println("请输入价格");
BigDecimal price = scanner.nextBigDecimal();
System.out.println("请输入书的页数");
Integer pageNum = scanner.nextInt();
allShopMessages.set(i, new Book(shopMessage.getId(), name, shopMessage.getType(), price, pageNum, author));
isSuccess = true;
}
break;
}
}
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("src/zong_he_test/shop.txt"));
) {
outputStream.writeObject(allShopMessages);
if (isSuccess) {
System.out.println("修改成功");
} else
System.out.println("修改失败");
colseOutput(outputStream);
} catch (Exception e) {
System.out.println("修改失败,产生了异常");
}
}
private void deleteShopMessage() {
List<ShopMessage> allShopMessages = getAllShopMessages();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入商品id");
int id = scanner.nextInt();
boolean isHas = allShopMessages.stream().anyMatch(item -> item.getId().equals(id));
if (isHas)
allShopMessages = allShopMessages.stream().filter(item -> !item.getId().equals(id)).collect(Collectors.toList());
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("src/zong_he_test/shop.txt"));
) {
outputStream.writeObject(allShopMessages);
if (isHas) {
System.out.println("删除成功");
} else {
System.out.println("删除失败,该商品不存在");
}
colseOutput(outputStream);
} catch (Exception e) {
System.out.println("删除失败,产生异常信息");
}
}
private void addShopMessage() {
ShopMessage shopMessage = produceShopMessage();
List<ShopMessage> allShopMessages = getAllShopMessages();
if (shopMessage == null) {
allShopMessages.add(shopMessage);
System.out.println("数据不能为null");
return;
}
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("src/zong_he_test/shop.txt"));
) {
outputStream.writeObject(allShopMessages);
System.out.println("添加成功");
colseOutput(outputStream);
} catch (Exception e) {
System.out.println("添加失败,产生异常信息");
}
}
private ShopMessage produceShopMessage() {
List<ShopMessage> allShopMessages = getAllShopMessages();
ShopMessage shopMessage = null;
System.out.println("请添加商品信息:");
Scanner scanner = new Scanner(System.in);
boolean isHas = false;
Integer id = 0;
do {
System.out.println("请输入商品编号:");
Integer inputId = scanner.nextInt();
isHas = allShopMessages.stream().anyMatch(item -> item.getId().equals(inputId));
id = inputId;
if (isHas) System.out.println("商品编号已存在,请重新录入");
} while (isHas);
System.out.println("请输入商品类型(1为书,2为手机):");
int whitchType = 0;
do {
whitchType = scanner.nextInt();
} while (whitchType != ShopType.BOOK_TYPE.getTypeId() && whitchType != ShopType.PHONE_TYPE.getTypeId());
if (whitchType == ShopType.PHONE_TYPE.getTypeId()) {
System.out.println("请输入手机名称");
String name = scanner.nextLine();
System.out.println("请输入价格");
BigDecimal price = scanner.nextBigDecimal();
System.out.println("请输入像素");
Integer cameraNum = scanner.nextInt();
shopMessage = new Phone(id, name, whitchType, price, cameraNum);
} else if (whitchType == ShopType.BOOK_TYPE.getTypeId()) {
System.out.println("请输入书名");
String name = scanner.next();
System.out.println("请输入作者名称");
String author = scanner.next();
System.out.println("请输入价格");
BigDecimal price = scanner.nextBigDecimal();
System.out.println("请输入书的页数");
Integer pageNum = scanner.nextInt();
shopMessage = new Book(id, name, whitchType, price, pageNum, author);
} else {
System.out.println("不可以输入该类型数据");
}
return shopMessage;
}
private List<ShopMessage> getAllShopMessages() {
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("src/zong_he_test/shop.txt"));
) {
List<ShopMessage> shopMessages = new ArrayList<>();
shopMessages = (List<ShopMessage>) objectInputStream.readObject();
System.out.println("------------请看商品列表-----------");
System.out.println(shopMessages);
colseInput(objectInputStream);
return shopMessages;
} catch (Exception e) {
System.out.println("没有商品信息");
return new ArrayList<ShopMessage>();
}
}
private void colseInput(InputStream stream) throws IOException {
if (stream != null) stream.close();
}
private void colseOutput(OutputStream stream) throws IOException {
if (stream != null) {
stream.flush();
stream.close();
}
}
}