018.day18 map集合如何实现排序 File类 IO流 字节流 字符流 编码
文件操作和IO流
目录
复习
一、map集合如何实现排序
// TODO HashMap - key为一个引用类型(自定义类型)
// 定义Hash结构的Map集合
Map<Student, Integer> student = new HashMap<>();
// 根据存放顺序有序
student.put(new Student("sand", 18), 5);
student.put(new Student("tom", 19), 2);
student.put(new Student("tom", 19), 3);
student.put(new Student("jerry", 20), 4);
for (Map.Entry<Student, Integer> temp : student.entrySet()) {
System.out.println(temp);
}
//Student [name=tom, age=19]=3
//Student [name=jerry, age=20]=4
//Student [name=sand, age=18]=5
// 将集合当中的元素重新排序
// 将一个Map结合中的键值对作为ArrayList的元素,直接转换为ArrayList
ArrayList<Map.Entry<Student, Integer>> list = new ArrayList<>(student.entrySet());
// list集合中存储顺序与map集合一致
for (Entry<Student, Integer> entry : list) {
System.out.println(entry);
}
//Student [name=tom, age=19]=3
//Student [name=jerry, age=20]=4
//Student [name=sand, age=18]=5
// 借助Collections工具类中的sort方法
// 第一个参数为List接口的实现类
// 第二个参数为匿名内部类实现的比较器,或者是实现了外部比较器接口的类的实例
Collections.sort(list, new StudentComparator());
// 当前的排序结果记录在list当中
for (Entry<Student, Integer> entry : list) {
System.out.println(entry);
}
//此时已经实现了排序,排序规则是在StudentComparator()中根据student类年龄升序排序
//Student [name=sand, age=18]=5
//Student [name=tom, age=19]=3
//Student [name=jerry, age=20]=4
public class StudentComparator implements Comparator<Entry<Student, Integer>> {
// 指定比较器的泛型为Map.Entry<Student, Integer>
@Override
public int compare(Entry<Student, Integer> o1, Entry<Student, Integer> o2) {
// o1代表当前对象,o2代表已经存在的对象
// Integer当中已经实现了自然排序接口,默认升序
// value中记录的是元素的存入顺序
// 可以直接调用compareTo方法使得集合按照map当中的value升序
// return o1.getValue().compareTo(o2.getValue());
// 针对key进行排序
// 如果key是一个自定义类型的实例,也可以在类的定义结构中实现自然排序接口,编写相应的比较规则,直接调用
return o1.getKey().compareTo(o2.getKey());
}
}
public int compareTo(Student o) {
// 需求:向TreeSet集合当中存放Student对象,使得对象之间根据年龄升序
// 当两个对象属性完全相同时,认定是重复对象,返回0
Student student = null;
// 1.判断实例的类型
if (o instanceof Student) {
student = (Student) o;
}else {
// 如果待比较元素不是Student类型,不予添加
return 0;
}
if (this.name.equals(student.getName()) && this.age == student.getAge()) {
// 当两个比较对象各属性相同时,认定相同,不予添加
return 0;
}
// 2.年龄的比较
if (this.age >= student.getAge()) {
// 通过正负来决定位置
// 正数放后
// 负数放前
return 1;
}else {
return -1;
}
}
- 练习
// TODO HashMap练习:1.根据商品价格升序/降序 2.根据存放顺序
Map<Product, Integer> map = new HashMap<>();
// 0.准备数据,构建集合
map.put(new Product("生活用品", 200), 1);
map.put(new Product("电子产品", 300), 2);
map.put(new Product("日常用品", 150), 3);
map.put(new Product("食品", 400), 4);
// 1.将集合转换为ArrayList
// 泛型与定义保持一致
ArrayList<Map.Entry<Product, Integer>> list = new ArrayList<>(map.entrySet());
// 2.自定义比较器,以一个实例的方式传入
Collections.sort(list, new ProductComparator());
// 3.输出结果
for (Map.Entry<Product, Integer> entry : list) {
System.out.println(entry);
}
Collections.sort(list, new Comparator<Map.Entry<Product, Integer>>() {
// value当中记录了存放顺序
// Integer实现类自然排序接口
// 默认升序与value的顺序一致
@Override
public int compare(Entry<Product, Integer> o1, Entry<Product, Integer> o2) {
// 获得值
return o1.getValue().compareTo(o2.getValue());
}
});
System.out.println();
// 3.输出结果
for (Map.Entry<Product, Integer> entry : list) {
System.out.println(entry);
}
- 数据分析的helloword
// TODO 单词计数:统计每个单词出现的总次数
// 0.数据准备
String[] lines = new String[4];
lines[0] = "good good study";
lines[1] = "day day up";
lines[2] = "I am sand";
lines[3] = "I am a boy good";
// 1.遍历数据 -> 获取到每一个单词
Map<String, Integer> result = new HashMap<>();
for (String line : lines) {
for (String word : line.split(" ")) {
// word -> 获取到的每一个单词
// 2.当第一次添加时,次数记为1;后续添加时,累加次数
// result.get(word) == null
// 当集合当中不包含相应的key(统计的单词) -> 第一次添加进来 -> 次数记为1
if (!result.containsKey(word)) {
result.put(word, 1);
}else {
// 非首次添加,将次数进行累加
// int count = result.get(word) + 1;
// result.put(word,count)
result.put(word, result.get(word) + 1);
}
}
}
// 3.输出结果
for (Map.Entry<String, Integer> temp : result.entrySet()) {
System.out.println(temp);
}
本节任务
I/O流
File类的使用
字节流和字符流
教学目标
了解I/O流的概念
掌握File类的使用
掌握字节流的使用
掌握字符流的使用
教学内容
一、File类
在Java中,使用File类对磁盘上的文件(夹)进行操作
1. 构造方法
2. 字段
- pathSeparator:与系统有关的路径分隔符,Windows下为分号,Linux下为冒号
- pathSeparatorChar:同上,以一个字符的形式存在
- separator:与系统有关的名称分隔符,Windows下为反斜杠,Linux下为斜杠
- separatorChar:同上,以一个字符的形式存在
3. 方法
// TODO File类-文件路径
// 创建File的实例,其中包含了路径信息
// File的构造器,以字符串的形式指定路径
File file1 = new File("E://test");
// 文件(文件夹)的绝对路径
System.out.println(file1.getAbsolutePath());//E:\test
// 文件(文件夹)的标准路径
System.out.println(file1.getCanonicalPath());//E:\test
// "" -> 空字符串时能够代表当前路径 -> 可以获得绝对路径 -> 不能执行文件相关操作
// "文件名/路径" -> 从当前路径开始,拼接当前路径,获得一个完整的路径
// "/" -> 斜杠:当前能够到达的根目录 (Windows:所在盘符/Linux:根路径)
// 使用斜杠时以程序执行的位置有关,会获取当前所在盘符
// TODO File类-构造器
File parent = new File("E://test");
// 直接传入File对象作为父级路径
File child1 = new File(parent, "b");
System.out.println(parent.getAbsolutePath());
System.out.println(child1.getAbsolutePath());
// 直接传入父级路径的字符串
File child2 = new File(parent.getPath(), "a");
System.out.println(child2.getAbsolutePath());
// TODO File类 - 文件查看 - 练习:输出某一路径下的所有文件(夹)
File file = new File("E://test");
// 判断路径是否存在
if (!file.exists()) {
// 不存在则创建一个文件夹,返回值为boolean类型
file.mkdir();
}
// 获取当前路径下的所有文件(夹)
getFiles(file);
}
/**
* 递归的方式获得某一路径下的所有文件(夹)信息
* @param file 指定某一个存在的路径
*/
public static void getFiles(File file) {
// 完全打印当前目录下的文件夹的内容
for (File child : file.listFiles()) {
// 打印当前目录下的子文件(夹)
System.out.println(child);
}
for (File child : file.listFiles()) {
// 判断该路径文件是否是一个文件夹
if (child.isDirectory()) {
// 如果子目录是一个文件夹,把其当成一个新的目录,遍历里面的子目录
getFiles(child);
}
}
// TODO 路径相关的分隔符
// 各级路径之间的分隔符(文件(夹)与文件(夹)之间的分隔符)
// Windows下可以使用/(斜杠)和\(反斜杠)
// Linux下只能使用斜杠
// 解决跨平台下的路径的兼容问题
System.out.println(File.separator);
// 路径与路径之间的分隔符
System.out.println(File.pathSeparator);
// TODO File的删除 - 非空目录
{
File file = new File("E:/test");
if (!file.exists()) {
file.mkdirs();
}
delete(file);
}
public static void delete(File file) {
// 如果是空文件夹则直接删除
if (file.delete()) {
return;
}else {
// 非空文件夹
for (File child : file.listFiles()) {
// 尝试删除文件及文件夹
// 对于非空文件夹会进入if结构
// 即使不触发if,删除行为也会进行,例如:删除文件时删除成功,此时if中的值为false
if (!child.delete()) {
delete(child);
}
}
// 删除所传入路径的最外层文件夹
file.delete();
}
// TODO File类的方法
File file = new File("E:/test");
System.out.println(file.canExecute());//执行:进入文件夹
System.out.println(file.canRead());//读->查看文件夹中的信息
System.out.println(file.canWrite());//写:修改文件夹内容
// 指定的路径文件不存在时,可以创建一个新的文件
File file2 = new File(file,"aa.txt");
file2.createNewFile();
// 创建所有不存在的父级目录
File file3 = new File(file,"qq/ww/ee");
file3.mkdirs();
// getParentFile() -> 父级路径的File对象,以最后一级目录作为当前路径,返回剩余的路径信息
System.out.println(file3.getParentFile().getAbsolutePath());//E:\test\qq\ww
File file4 = new File("E:/");
// 当前路径已经在根目录,则返回null(返回空对象)
// getParent -> 获取父级路径的字符串
System.out.println(file4.getParentFile());//null
二、IO流
I:input,O:output,通常指对磁盘文件内容的读写操作,通过一个媒介或管道将文件与内存进行交互,这个媒介就被成为I/O流,流的本质为数据传输
1. 按流向分类
- 输入流:将数据读取到程序(内存)中使用的流
- 输出流:从程序(内存)向外输出数据使用的流
2. 按单位分类
- 字符流:一次传输一个字符的数据,将数据以字符的形式传输
- 字节流:一次传输一个字节的数据,将数据以字节的形式传输
3. 按层次分类
- 节点流:可以从/向一个特定的节点读写数据的流
- 处理流:对已存在的流的封装,通过封装的流的功能调用实现数据读写
三、字节流
1. 字节输入流
InputStream是一个抽象类,不能直接实例化对象
- 相关方法
// TODO 字节流 - 读取文件(输入流)
// 借助工具类 -> 每次读取若干个字节(一个或多个) -> 将读取到的数据转换为char类型输出原信息
// 中文字符需要编码处理
// 1.指定一个要读取的文件路径
File file = new File("E:/test.txt");
// 2.初始化相关的对象
InputStream inputStream = new FileInputStream(file);
int i = -1;
String result = "";
// 3.读取文件中所有的内容 - read()方法
// 赋值的同时进行判断 -> 将获取到的值记录在某一个变量中,判断时直接使用变量值判断,同时变量值可以正常使用
while((i = inputStream.read()) != -1) {
result += (char)i;
}
// 4.将读取到的信息输出
System.out.println(new String(result.getBytes("iso-8859-1"),"UTF-8"));
inputStream.close();
2. 字节输出流
OutputStream是抽象类,不能直接实例化对象
// TODO 字节输出流
// 输出到磁盘的文件中
// 输出流重要参数:是否续写(apped),默认为flase
// 1.指定文件路径
File file = new File("E:/test.txt");
// 2.初始化字节输出流
OutputStream outputStream = new FileOutputStream(file,true);
// \r\n -> 在Windows中使用\r\n作为换行符,Linux中使用\n
// 3.向路径中写入信息(以byte形式)
outputStream.write("\r\n789续写".getBytes());
outputStream.close();
-
小练习
-
字节流文件复制
// TODO 字节流文件复制 public static void main(String[] args) throws IOException { copy("f:/test.txt", "f:/aa/testCopy.txt"); } /** * 字节流文件复制 * * @param src * 源文件路径 * @param dest * 目标文件路径 */ public static void copy(String src, String dest) { // 1.使用File指定文件路径 File srcFile = new File(src); File destFile = new File(dest); // 2.文件校验 // 源文件不存在,方法直接结束 if (!srcFile.exists()) { System.out.println("源文件不存在"); return; } // 目标文件路径父级路径不存在 if (!destFile.getParentFile().exists()) { destFile.getParentFile().mkdirs(); } // 目标文件不存在则创建 if (!destFile.exists()) { try { destFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } // 3.初始化输入输出流 InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = new FileInputStream(srcFile); outputStream = new FileOutputStream(destFile,false); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 4.文件复制 // length代表本次读取到的字节数,-1时代表到达文件末尾 int length = -1; // 将每次读取到的数据放入byte数组当中 // 使用字节数组的方式可以提高读写效率 byte[] data = new byte[1024]; try { while ((length = inputStream.read(data)) != -1) { // 写入byte数组中的数据,同时指定偏移量,从数组开头,一直到当前从源文件中读取的数据长度 outputStream.write(data,0,length); } // 5.关闭输入输出流 inputStream.close(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } }
-
字节流练习:文件内容比较
public static void main(String[] args) { // TODO 字节流练习:文件内容比较 System.out.println(compare("E:/test.txt", "E:/testCopy.txt")); } /** * 比较两个文件的内容是否完全一致 * @param c1 比较文件A * @param c2 比较文件B * @return 两个文件是否相同 */ public static boolean compare(String c1,String c2) { // 1.使用File指定文件路径 File cFile1 = new File(c1); File cFile2 = new File(c2); // 2.文件校验 if (!cFile1.exists() || !cFile2.exists()) { System.out.println("请检查源文件路径"); return false; } // 3.初始化输入流 InputStream inputStreamC1 = null; InputStream inputStreamC2 = null; try { inputStreamC1 = new FileInputStream(cFile1); inputStreamC2 = new FileInputStream(cFile2); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 4.文件内容比较 int i = -1; int j = -1; // 结果初始值为true boolean result = true; try { // 每次用i和j记录读取到的数据,当达到文件末尾时,值为-1 // &&/||逻辑运算有时会出现短路,当第一个结果已经能够决定整个表达式运算结果时,第二个表达式不会执行 // 可以使用位运算符 while ((i = inputStreamC1.read()) != -1 | (j = inputStreamC2.read()) != -1) { // 每次读取到的字符进行比较 if (i != j) { // 如果出现不相等,两个文件不相同,直接跳出 result = false; break; } } // 当某一个文件提前结束,i和j当中一定有一个为-1 // 此时i和j不相等 /*if (i != j) { result = false; }*/ } catch (IOException e) { e.printStackTrace(); } return result; }
-
3. 文件输入流
以字节流的形式读取一个文件中的数据
-
FileInputStream:使用File作为参数初始化,指定要读取的文件
-
read()方法:每次读取一个字节
// TODO 字符流输入流 // 1.初始化字符流 // 需要一个InputStream -> 实现类 Reader reader = new InputStreamReader(new FileInputStream(new File("f:/test.txt"))); // 2.读取文件中的数据 int i = -1; while ((i = reader.read()) != -1) { System.out.print((char)i); } // 3.关闭流 reader.close();
// TODO 字符流一次读入多个字符
// 1.初始化字符流
// 需要一个InputStream -> 实现类
Reader reader = new InputStreamReader(new FileInputStream(new File("f:/test.txt")));
// 2.读取文件中的数据
// 当前次读取到的字符个数
int length = -1;
// 每次读取的最大字符数
char[] cs = new char[5];
while ((length = reader.read(cs)) != -1) {
// 最后一次(临近文件末尾)不一定会装满字符数组,此时以length为准
for (int j = 0; j < length; j++) {
System.out.print((char) cs[j]);
}
}
// 3.关闭流
reader.close();
4. 文件输出流
- FileOutputStream:使用File作为参数初始化,指定要写入的文件
- write(byte[] b)方法:从指定byte数组中将数据写入此文件输出流中
// TODO 字符流的输出流
// 1.初始化字符输出流
Writer writer = new OutputStreamWriter(new FileOutputStream(new File("E:/test.txt"),true));
// 2.使用字符流的方式写入数据
writer.write("!字符流输出!");
// 3.使用flush方法将缓冲区的数据写入文件
writer.flush();
// 流关闭时也会释放缓冲区
// 4.关闭流
writer.close();
- 小练习
// TODO 使用字符流实现文件的复制
copy("E:/test.txt", "E:/aa/bb/testCopy.txt");
}
public static void copy(String src,String dest) {
// 1.初始化文件路径
File srcFile = new File(src);
File destFile = new File(dest);
// 2.路径校验
if (!srcFile.exists()) {
System.out.println("源文件不存在");
return;
}
if (!destFile.getParentFile().exists()) {
// 自动创建父级目录
destFile.getParentFile().mkdirs();
}
// 3.初始化需要的流
Reader reader = null;
Writer writer = null;
try {
reader = new InputStreamReader(new FileInputStream(srcFile));
writer = new OutputStreamWriter(new FileOutputStream(destFile));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 4.字符流方式文件复制
int length = -1;
char[] data = new char[5];
try {
while ((length = reader.read(data)) != -1) {
writer.write(data, 0, length);
writer.flush();
}
// 5.关闭输入输出流
reader.close();
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
四、字符流
1. 输入流
抽象父类:Reader
- 子类:InputStreamReader,FileReader
- 初始化字符输入流:需要一个文件流
- 初始化文件流:需要一个File对象(包含路径信息)
// 细化写法
File file = new File("E:/test.txt");
InputStream inputStream = new FileInputStream(file);
Reader reader = new InputStreamReader(inputStream);
// 简化写法
Reader reader = new InputStreamReader(new FileInputStream(new File("E:/test.txt")));
- read():每次读取一个字符(中英文均可),读取到文件末尾返回-1
- 可以一次读取多个字符
2. 字符输出流
2. 输出流
抽象父类:Writer
- 子类:OutputStreamWriter,FileWriter
- 初始化字符输出流:需要一个文件流
- 初始化文件流:需要一个File对象(包含路径信息)
// 细化写法
File file = new File("E:/test.txt");
OutputStream inputStream = new FileOutputStream(file);
Reader reader = new OutputStreamWriter(inputStream);
// 简化写法
Writer writer = new OutputStreamWriter(new FileOutputStream(new File("E:/test.txt")));
- write(String str):可以直接向目标写入字符串
- flush():刷新缓冲,对于有缓冲区的流都应调用该方法
五、编码
文本内容必须经过正确的编码和解码才能够正常显示,但有些时候我们可以通过ISO-8859-1编码来获得一个正确的表示
- 字符串编码:getBytes(String charsetName)方法获得一个byte数组
- 解码显示:new String(byte[] bytes,charsetName)
- 解码方式和编码方式相同时,可以正常显示
- UTF-8以及GBK格式经过ISO-8859-1解码再编码后,数据不丢失
- UTF-8格式经过GBK解码再编码后,数据不丢失