4 Java 核心类库
4.1 泛型
泛型,即“参数化类型”。就是将原来具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
作用:
- 提高代码的复用率
- 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
4.1.1 泛型类
public class ClassName<T> {
private T data;
public T getData() {
return data;
}
public void setData() {
this.data = data;
}
}
4.1.2 泛型接口
public interface InterfaceName<T> {
T getData();
}
// 实现接口时可以选择指定泛型类型,也可以不指定:
// 指定类型:
public class Interface1 implements InterfaceName<String> {
private string text;
@Override
public String getData() {
return text;
}
}
// 不指定类型:
public class Interface2<T> implements InterfaceName<T> {
private T data;
@Override
public T getData() {
return data;
}
}
4.1.3 泛型方法
private static <T> T methodName(T a, T b) {}
// e.g.
public static <T> void print(T a) {
system.out.println(a);
}
4.1.4 泛型限制类型
指定泛型的限定区域,例如: 必须是某某类的子类或 某某接口的实现类:
// 格式:<T extends 类或接口1 & 接口2>
// e.g.
public static void main(String[] args) {
Plate<Apple> p = new Plate<>();
}
interface Fruit{}
class Apple implements Fruit{}
class Plate<T extends Fruit> {
T data;
}
4.1.5 通配符 ?
-
<? extends Parent>
指定了泛型类型的上界 -
<? super Child>
指定了泛型类型的下界 -
<?>
指定了没有限制的泛型类型
Plate<? extends Fruit> p = new Plate<Apple>(); // 上界
Plate<? super Apple> p = new Plate<Fruit>(); // 下界
注意:在编译之后程序会采取去泛型化的措施,也就是说Java中的泛型只在编译阶段有效,而不会进入到运行时阶段。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并在对象进入和离开方法的边界处添加类型检查和类型转换的方法。
4.2 常见类库
4.2.1 java.util.Objects
- 此类包含static实用程序方法(null或null防范,用于计算对象的哈希代码,返回对象的字符串,比较两个对象,检索索引或子范围值是否超出范围),用于操作对象或在操作前检查某些条件
- 源码:
// static boolean equals(Object a, Object b)
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
// static boolean isNull(Object obj)
public static boolean isNull(Object obj) { return obj == null; }
// static boolean nonNull(Object obj)
public static boolean isNull(Object obj) { return obj != null; }
// static boolean requireNonNull(T obj):检查对象引用是否不是null
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException(); //如果是空,直接抛出异常
return obj;
}
4.2.2 java.lang.Math
//四舍五入
Math.round(-100.5); // -100
4.2.3 java.util.Arrays
binarySearch, compare, equals, sort, toString, copyOf
int[] arr = {2,3,4,5,1};
System.out.println(arr); //打印的是内存地址(哈希值)
System.out.println(Arrays.toString(arr)); //打印[2,3,4,5,1]
Arrays.sort(arr); //排序
Arrays.binarySearch(arr, 6); //二分查找
arr = Arrays.copyOf(arr, 15); //扩容至长度为15
4.2.4 java.math.BigDecimal
实现小数的精准运算
// 构造方法
public BigDecimal(String val) {}
// 常用方法
public BigDecimal add(BigDecimal augend);
public BigDecimal subtract(BigDecimal augend);
public BigDecimal multiply(BigDecimal augend);
public BigDecimal devide(BigDecimal augend);
// e.g.
BigDecimal b1 = new BigDecimal("0.1");
BigDecimal b2 = new BigDecimal("0.2");
BigDecimal b3 = b1.add(b2); // 0.3
double d = b3.doubleValue(); // 转换为double类型
4.2.5 java.util.Date
表示特定的时刻,精度为毫秒
构造方法:
Date()
当前时间Date(long date)
方法:
long getTime()
返回自1970年1月1日00:00:00GMT以来的毫秒数void setTime(long time)
设置时间点
4.2.6 java.text.DateFormat
- 用于格式化和解析日期字符串
- 是一个抽象类,使用子类
SimpleDateFormat
- 常用方法:
String format(Date date)
格式化日期Date parse(String source)
解析日期字符串
/**
* y: 年
* M: 月
* d: 日
* H: 时
* m: 分
* s: 秒
*/
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String text = format.format(new Date()); // 将date对象格式化为字符串
Date date = format.parse("2021-12-12 12:12:12"); // 将符合格式的字符串转化为date对象,可用于计算时间差
4.2.7 java.util.Calendar
- 是一个抽象类,通过其
getInstance()
方法创建对象 - 年月日时分秒都存储在
filed
数组里,通过传入下标(例如Calendar.YEAR
)用get()
方法获取 - 常用方法:
set
get
add
getTime
获取日历时间表示的Date对象getActualMaximum
获取某字段的
Calendar cl = Calendar.getInstance(); //创建对象
int year = cl.get(Calendar.YEAR); //2020
int year = cl.get(Calendar.MONTH); //0-11
int day = cl.get(Calendar.DAY_OF_YEAR); //一年的第几天,从0开始
cl.set(Calendar.YEAR, 2021); //设置年为2021
cl.add(Calendar.YEAR, 1); //设置年+1
Date d = cl.getTime(); //获取日历时间表示的Date对象
int m = cl.getActualMaximum(Calendar.DAY_OF_MONTH); //当前月份的最大值
4.2.8 java.lang.System
常用方法:
gc()
运行垃圾回收器exit(int status)
终止当前运行的Java虚拟机static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
复制数组
4.2.9 java.lang.String
-
字符串是不变的,他们的值在创建后无法更改
String str = "abc"; // 相当于 char data[] = {'a', 'b', 'c'}; string str = new String(data);
-
String
类 用final
修饰,不能被继承 -
两个字符串内容如果完全相同,则它们采用同一块内存地址(即可共享);但如果 是通过
new
创建的对象,一定是新开辟的空间 -
字符串常量池(存在方法区中)
1. 方法区Method Area(加载代码的内存区),又称永久代Permanent Generation,被所有线程共享 2. 堆heap 1) 一个JVM实例只存在一个堆内存,大小是可以调节的; 2) 类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行; 3) 堆 在逻辑上分为三部分(Perm): - 新生代 YoungGen:存刚创建的对象,gc回收很快 - 老年代 OldGen:存常用的对象(在新生代中连续15次没有被回收) - 永久代 PermGen:类、方法、常量、static修饰的(不会被垃圾回收) 3. JDK1.8演变 字符串常量池还在堆,运行时常量池还在方法区,只不过方法区的实现从永久代变成了元空间Metaspace; 元空间与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中
-
一种构造方法:
String(byte[] bytes, Charset charset)
使用charset字符集解码字节数组 -
方法:
int compareTo(String anotherString)
按字典顺序比较两个字符串boolean contains(CharSequence s)
是否包含指定的char值序列int indexOf(int ch)
指定字符第一次出现在字符串中的索引String trim()
删除首尾空格
-
字符串拼接:
- 每+一次就在内存中创建一个新的String对象,即产生一次垃圾(在永久代里不会被回收)
- 因此不应该使用
String
,应该使用StringBuffer / StringBuilder
String, StringBuilder, StringBuffer 的区别: 1. String 是字符串常量,不可变,修改时会创建了一个新的String对象,然后将指针指向它 2. StringBuffer 和 StringBuilder 是字符串变量,使用时会对对象本身进行操作(会动态扩容),而不是生成新的对象再改变对象引用,最后可以用 toString()方法转成 String 3. StringBuffer 是线程安全的;而 StringBuilder 不是(不能保证同步),速度更快,单线程时使用 4. 在某些特别情况下, String 对象的字符串拼接其实被 Java Compiler 编译成了 StringBuffer 对象的拼接,所以这时 String 对象的速度不比 StringBuffer 对象慢,例如: String s1 = "This is only a" + "simple" + "test"; StringBuffer Sb = new StringBuilder("This is only a" ).append("simple").append("test"); 其实在 Java Compiler 里,自动做了如下转换: String s1 = "This is only a simple test"; 但如果拼接的字符串来自另外的 String 对象的话,Java Compiler 就不会自动转换了,速度也就没那么快了,例如: String s2 = “This is only a”; String s3 = “ simple”; String s4 = “ test”; String s1 = s2 + s3 + s4;
4.3 集合
4.3.1 Collections
- Java类集中保存单值的最大操作接口
- 子接口:List,Set(区分集合中是否允许有重复元素)
4.3.2 List
- 常用的实现类:ArrayList, Vector, LinkedList
- 常用方法:
E get(int index)
根据索引位置取出元素int indexOf(Object o)
查找指定对象的位置void add(int index, E element)
在指定位置添加元素E remove(int index)
删除指定位置的元素(重载了继承的父类Collections中的boolean remove(Object o)
方法)
ArrayList
-
构造方法:
- 无参(初始容量10):先构造一个长度为0的列表,而添加一个元素时会自动扩容
- 一参(指定初始容量)
ArrayList(Collection<? extends E> c)
构造包含指定集合元素的列表
-
boolean add(E e)
一定返回true-
扩容:
private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); } private int newCapacity(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍 //如果计算的新长度比需要的长度小(还不够存):0*1.5=0, 1*1.5=1, 添加一组数据,长度不可控 if (newCapacity - minCapacity <= 0) { // 如果是第一次创建,没有传长度 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); // 默认长度(10)与minCapacity(第一次为1)取最大 } if (minCapacity < 0) throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); // MAX_ARRAY_SIZE= Integer.MAX_VALUE-8 }
-
Vector
-
构造方法中可以指定容量增量(即比ArrayList多一种构造方法):
Vector(int initialCapacity, int capacityIncrement)
ArrayList, Vector, LinkedList 区别
链表(LinkedList) vs 数组(ArrayList/Vector)
1. 数组是连续存储的,链表不必相连(灵活地分配内存空间)
2. 数组查找快(通过下标查询),增删慢(需要移动元素);链表查找慢 O(n),增删快 O(1)
3. ArrayList和Vector都是基于动态数组实现的(通过新数组覆盖老数组的方式扩容),而LinkedList是通过链表实现的
ArrayList vs Vector
1. ArrayList是线程不安全的,速度较快;而Vector是线程安全的,是同步的
2. Vector比ArrayList多了一种构造方法,可以指定容量增量(默认为一倍);而ArrayList的增量为0.5倍,不能指定
4.3.3 Iterator, ListIterator 迭代器
- Iterator 用于迭代 Collections的所有集合(List, Set)
- ListIterator 只能迭代 List的集合
- 可以控制指针往前走:
previous()
- 可以插入元素(插入到
next()
返回的元素之前):add(E e)
- 可以修改当前指针指定的数据:
set(E e)
- 可以控制指针往前走:
ArrayList<Integer> data = new ArrayList<>();
data.add(1);
data.add(2);
data.add(3);
Iterator<Integer> iterator = data.iterator();
while (iterator.hasNext()) {
Integer i = iterator.next();
}
// remove前需要先获取:
Iterator<Integer> iterator = data.iterator();
iterator.next();
iterator.remove();
forEach
-
用于迭代数组或Colleciton的集合(用迭代器)
int[] arr = {5,4,3,2,1} for (int data : arr) { System.out.println(data); } // ArrayList<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); for (String s: list) { System.out.println(s); }
4.3.4 Set
- 不包含重复元素(包括null)
- 获取元素方法(没有
get(int index)
方法,即不能通过传入下标的方式查找数据):iterator()
方法得到迭代器toArray()
变成数组
HashSet
-
散列存放(哈希表/散列表,内部有一个HashMap对象),无序存储
-
单值存储,即重复利用了双值存储的HashMap:
private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT) == null; }
TreeSet
-
二叉树存储(基于TreeMap),有序(根据数据的顺序)
-
其iterator方法返回的迭代器是快速失败的:在并发修改的情况下,迭代器快速而干净地失败
-
快速失败:如果在创建迭代器之后修改了集合(除了通过迭代器自己的remove方法),迭代器将抛出
ConcurrentModificationException
-
安全失败(通常):遍历的是复制的集合,所以迭代时不会失败
-
TreeSet<String> data = new TreeSet<>();
data.add("c");
data.add("a");
data.add("b");
for (String s: data) {
System.out.println(s); // a b c
}
4.3.5 Map<K, V>
-
存储一个个的键值对数据
-
键key 不可重复(所以Set的内部都使用了Map)
-
每个键最多可映射一个值
-
方法:
Set<K> keySet()
得到键的set- 遍历:对key进行迭代,调用
V get(Object key)
取到值 - 存储:
V put(K key, V value)
如果产生了替换,会返回旧值,否则返回null - 删除:
V remove(Object key)
default boolean remove(Object key, Object value)
HashMap
-
实现:哈希桶:对象数组+链表/红黑树
-
取模:hashCode()%N (初始桶数量16,则得到0-15的下标/索引,存到对应位置)
-
数组的每个元素都是一个链表/红黑树(解决哈希值冲突问题)
-
1.8优化:当哈希桶(链表)中的数据量>8,链表会转化成红黑树(更利于查找);
当哈希桶中的数据量减少到6时,从红黑树转换为链表
-
散列因子0.75:如果桶中有75%存了数据,扩容一倍(*2),可以指定
- 过小:浪费内存空间
- 过大:查询效率低
-
扩容:散列,重建
-
-
搜索:不需要遍历,只需得到hashcode取余运算得到下标
Hashtable, HashMap, ConcurrentHashMap, LinkedHashMap 区别
1. HashMap线程不安全(同时,不保证同步),效率高;Hashtable线程安全(排队机制),效率低
2. ConcurrentHashMap:采用分段锁机制保证线程安全,效率又比较高(只有当操作的是同一下标的桶时才需要排队)
HashMap不保证存储顺序(因为hashcode计算),LinkedHashMap能保证存储顺序(会同时存进双向链表中)
4.4 IO
4.4.1 java.io.File
- 文件和目录路径名的抽象表示
- 常用构造方法:
File(String pathname)
File(File parent, String child)
文件夹,新文件名称File(String parent, String child)
- 字段:
static String pathSeparator
与系统相关的路径分隔符static String separator
与系统相关的名称分隔符
- 常用方法:
boolean delete()
boolean deleteOnExit()
boolean createNewFile()
创建指定新文件(指定文件不存在时)boolean mkdir()
创建目录boolean mkdirs()
创建目录(包括不存在的父目录)String getAbsolutePath()
获取文件/目录的绝对路径(从盘符开始)String getPath()
返回的是定义时的路径String getName()
获取文件/目录名称String getParent()
获取父目录名称File getParentFile()
获取父目录对象long length()
获取文件的大小(字节)boolean exists()
判断文件/目录是否存在boolean isDirectory()
判断对象是否为目录boolean isFile()
判断对象是否为文件File[] listFiles()
获取目录中所有的对象String[] list()
获取目录中所有的对象名称boolean renameTo(File deat)
重命名/移动
// 获取字段
System.out.println(File.pathSeparator); // ; 路径分隔符
System.out.println(File.separator); // \ 名称分隔符
// 构造方法1,创建新文件夹
File dir = new File("c://haha");
dir.mkdir();
// 构造方法2,创建新文件
File a = new File(dir, "a.txt");
a.createNewFile();
// 构造方法3,创建新文件
File b = new File("c://haha", "b.txt");
b.createNewFile();
//删除
a.delete();
b.delete();
-
文件遍历
public static void main(String[] args) throws IOException { File e = new File("e:\\"); File[] files = e.listFiles(); listFiles(files); } // 遍历目录,删除所有200MB以上的avi文件 public static void listFiles(File[] files) { if (files != null && files.length>0) { for (File file : files) { if (file.isFile()) { // 是文件 if (file.getName().endsWith(".avi")) { // 找到了一个avi文件 if (file.length() > 200*1024*1024) { file.delete(); System.out.println(file.getAbsolutePath()+" 已删除"); } } } else { // 文件夹 File[] files2 = file.listFiles(); listFiles(files2); // 递归遍历子文件夹 } } } }
文件过滤器 FileFilter
public static void listFiles(File dir) {
// 1. 创建一个过滤器 并 描述规则
FileFilter filter = new FileFilter() { // 匿名内部类,只使用一次
@Override
public boolean accept(File pathname) {
if (pathname.getName().endsWith(".avi") || pathname.isDirectory())
return true;
return false;
}
};
// 2. 获取文件,遍历
File[] files = dir.listFiles(filter);
if (dir != null && dir.length>0) {
for (File file : dir) {
if (file.isDirectory())
listFiles(file);
else
System.out.println(file.getAbsolutePath());
}
}
}
4.4.2 IO流 概述
- 将数据传输操作看作一种数据的流动,按照流动的方向分为输入Input和输出Output
- Java中的IO操作主要指的是java.io包下的一些常用类的使用,通过这些常用类对数据进行读取(输入Input) 和 写出(输出Output)
- 数据传输时都是以二进制形式存储的
- IO流的分类:
- 按方向分:输入流 输出流
- 按流动的数据类型:
- 字节流:
- 输入流:InputStream
- 输出流:OutputStream
- 字符流:
- 输入流:Reader
- 输出流:Writer
- 字节流:
4.4.3 字节流
java.io.OutputStream
- 字节输出流的所有类的超类
- 常用方法:
void close()
关闭此输出流并释放与该流相关的所有系统资源void flush()
刷新此输出流并强制写出任何缓冲的输出字节void write(byte[] b)
将所有字节从指定字节数组写入到此输出流void write(byte[] b, int off, int len)
将从偏移量off开始的指定字节数组中的len个字节写入输出流abstract void write(int b)
将指定的字节写入此输出流(写入的字节是b的八个地位)
java.io.FileOutputStream
- OutputStream的常用子类
- 构造方法:
FileOutputStream(File file)
FileOutputStream(File file, boolean append)
append=true表示接着原来的写FileOutputStream(String name)
FileOutputStream(String name, boolean append)
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("c://a.txt");
fos.write(65); // A
byte[] bytes = {65,66,67,68,69};
fos.write(bytes); // AABCDE
fos.close();
}
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("c://a.txt");
byte[] bytes = {65,66,67,68,69};
fos.write(bytes); // ABCDE; 如果改成FileOutputStream("c://a.txt", true)则为AABCDEABCDE
fos.close();
}
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("c://a.txt");
byte[] bytes = "ABCDE".getBytes();
fos.write(bytes, 1, 2); // BC
fos.close();
}
java.io.InputStream
- 字节输入流:把硬盘中的文件读取输入到内存中
- 常用方法:
abstract int read()
从输入流中读取下一个数据字节(返回值范围0-255;如果由于达到流末尾而没有可用字节,则返回 -1)int read(byte[] b)
从输入流中读取一些字节数并存储到缓冲区数组bvoid close()
java.io.FileInputStream
- InputStream的常用子类
- 构造方法:
FileInputStream(File file)
FileInputStream(String name)
public static void main(String[] args) {
FileInputStream fis = new FileInputStream("c://a.txt"); // abcdef
// 一次读取一个字节
byte b = (byte) fis.read(); // 97
char b2 = (char) fis.read(); // b
// 循环读取一个字节直至结束:cdef
while (b != -1) {
byte b = (byte) fis.read();
System.out.print((char) b);
}
fis.close();
}
public static void main(String[] args) {
FileInputStream fis = new FileInputStream("c://a.txt"); // a-z
// 一次读取10个字节
byte[] bytes = new byte[10];
fis.read(bytes);
System.out.println(new String(bytes)); //abcdefghij
fis.read(bytes);
System.out.println(new String(bytes)); //klmnopqrst
fis.read(bytes);
System.out.println(new String(bytes)); //uvwxyzqrst 放在数组的前6位,后面的没有清空
fis.close();
}
解决该问题:
public static void main(String[] args) {
FileInputStream fis = new FileInputStream("c://a.txt"); // a-z
// 一次读取一组(10个)字节(常用,减少IO频率)
byte[] bytes = new byte[10];
int len = fis.read(bytes);
while (len != -1) {
System.out.println(new String(bytes, 0, len));
len = fis.read(bytes);
}
/* abcdefghij
klmnopqrst
uvwxyz
*/
fis.close();
}
文件加密和解密
加密/解密用异或运算:任何数据^相同的数字两次=其本身
public static void main(String[] args) {
System.out.print("请输入文件存储的全路径:");
Scanner input = new Scanner(System.in);
String fileName = input.nextLine();
// 原文件: a.png
File oldFile = new File(fileName);
// 加密存储的新文件: mi-a.png
File newFile = new File(oldFile.getParentFile() + "mi-" + oldFile.getName());
FileInputStream fis = new FileInputStream(oldFile);
FileOutputStream fos = new FileOutputStream(newFile);
while (true) {
int b = fis.read();
if (b == -1) break;
fos.write(b^10);
}
System.out.println("加密或解密完成");
fis.close();
fos.close();
}
4.4.4 字符流
- 以字符为单位,只能操作文字
Writer 字符输出流
- 常用方法:
abstract void close()
Writer append(CharSequence csq)
将指定字符串序列追加到此Writervoid write(int c)
写一个字符void write(char[] cbuf)
写一个字符数组abstract void write(char[] cbuf, int off, int len)
写一个字符数组的一部分void write(String str)
写一个字符串void write(String str, int off, int len)
写一个字符串的一部分
FileWriter
- 构造方法:
FileWriter(File file)
FileWriter(File file, boolean append)
FileWriter(File file, Charset charset)
public static void main(String[] args) {
FileWriter fw = new FileWriter("c://b.txt");
fw.write('a');
fw.write("床前明月光");
FileWriter fw2 = (FileWriter) fw.append("锄禾日当午"); //fw==fw2
fw2.append(", ").append("汗滴禾下土");
fw.close();
}
flush刷新管道
-
操作字符输出流时需要注意:
输出方(内存)给输入方(硬盘)发送的过程中,有一个缓存
-
字符输出的时候,刷新缓存空间,强制把缓存空间的内容写出到文件:
fw.flush()
-
close()
时会自动刷新
Reader 字符输入流
- 常用方法:
abstract void close()
int read()
一次读取一个字符int read(char[] cbuf)
一次读取一组字符至数组int read(char[] cbuf, int off, int len)
一次读取字符至数组的一部分
FileReader
- 构造方法:
FileReader(String fileName)
FileReader(String fileName, Charset charset)
FileReader(File file)
public static void main(String[] args) {
FileReader fr = new FileReader("b.txt");
// 循环一次读一个字符
while (true) {
int c = fr.read();
if (c == -1) break;
System.out.println((char) c);
}
fr.close();
}
public static void main(String[] args) {
FileReader fr = new FileReader("b.txt");
// 循环一次读一组字符到数组
char[] chars = new char[100]; //注意:默认为0(空格)
fr.read(chars);
System.out.println(new String(chars)); //不足100的话后面跟着空格
fr.close();
}
解决:
public static void main(String[] args) {
FileReader fr = new FileReader("b.txt");
// 循环一次读一组字符到数组
char[] chars = new char[100]; //注意:默认为0(空格)
int len = fr.read(chars);
System.out.println(new String(chars, 0, len)); //不足100的话后面跟着空格
fr.close();
}
4.4.5 转换流 InputStreamReader
- 将字节流 装饰为 字符流:使用了装饰者设计模式
- 输入流转换:
public static void main(String[] args) {
FileInputStream fis = new FileInputStream("c://a.txt");
// 将字节输入流 转换为 字符输入流
// 参数1:要转换的字节流; [参数2:编码名称]
InputStreamReader isr = new InputStreamReader(fis, "gbk");
while (true) {
int c = isr.read();
if (c == -1) break;
System.out.print((char) c);
}
}
- 输入流转换:
public static void main(String[] args) {
FileInputStream fis = new FileInputStream("c://a.txt");
// 将字节输入流 转换为 字符输入流
// 参数1:要转换的字节流; [参数2:编码名称]
InputStreamReader isr = new InputStreamReader(fis, "gbk");
while (true) {
int c = isr.read();
if (c == -1) break;
System.out.print((char) c);
}
isr.close();
}
- 输出流转换:
public static void main(String[] args) {
FileOutputStream fos = new FileOutputStream("c://a.txt");
// 将字节输出流 转换为 字符输出流
OutputStreamWriter osw = new OutputStreamWriter(fos);
osw.write("锄禾日当午,汗滴禾下土");
osw.flush();
osw.close();
}
4.4.6 打印流 PrintStream, PrintWriter
- 字符输出(System.out)
- PrintStream 是 FileOutputStream 的子类;PrintWriter 是 Writer 的子类
PrintStream ps = new PrintStream("c://c.txt");
ps.println("锄禾日当午,汗滴禾下土");
PrintWriter pw = new PrintWriter("c://c.txt");
pw.println("锄禾日当午,汗滴禾下土");
pw.flush(); //字符流需要刷新管道
-
也可以用于转换字节流(建议)
FileOutputStream fos = new FileOutputStream("c://c.txt"); PrintWriter pw = new PrintWriter(fos); pw.println("锄禾日当午,汗滴禾下土"); pw.flush();
4.4.7 缓存读取流 BufferedReader
- 将字符输入流转换为带有缓存,可以一次读取一行的缓存字符读取流
- 读到结尾时返回null
FileReader fr = new FileReader("c://c.txt");
BufferedReader br = new BufferedReader(fr);
String text = br.readLine();
System.out.println(text);
4.4.8 收集异常日志
try {
String s = null;
s.toString();
} catch (Exception e) {
PrintWriter pw = new PrintWriter("c://bug.txt");
// 打印日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-dd HH:mm:ss");
pw.println(sdf.format(new Date()));
// 写入异常
e.printStackTrace(pw);
pw.close();
}
4.4.9 Properties
-
是HashTable的子类,是Map集合
-
.properties文件:一行存储一个键值对,可以存储多行
-
方法:
synchronized Object put(Object key, Object value)
存储键值对synchronized void load(Reader reader)
把properties文件加载成程序中的Map集合synchronized void load(InputStream inStream)
void store(Writer writer, String comments)
存储为properties文件,字符串为注释void store(OutputStream out, String comments)
-
存储:
public static void main(String[] args) throws IOException { Properties ppt = new Properties(); ppt.put("name", "金苹果"); ppt.put("info", "讲述了苹果种植的过程"); FileWriter fw = new FileWriter("c://book.properties"); ppt.store(fw, "存储的图书"); fw.close(); }
book.properties:
#\u5B58\u50A8\u7684\u56FE\u4E66 #Sun Aug 23 00:27:56 CST 2020 name=金苹果 info=讲述了苹果种植的过程
-
读取:
public static void main(String[] args) throws IOException { Properties ppt = new Properties(); Reader r = new FileReader("c://book.properties"); ppt.load(r); //System.out.println(ppt.get("name")); //System.out.println(ppt.get("info")); System.out.println(ppt.getProperty("name")); System.out.println(ppt.getProperty("info")); }
4.4.10 序列化技术
-
对象序列化:将 Java 对象的状态转换为字节数组,以便存储或传输
-
反序列化:将字节数组转换回 Java 对象原有的状态
-
实现:该类需要实现Serializable接口,其包含的对象的类也需要实现Serializable接口
public static void main(String[] args) throw IOException { // 序列化 Book b = new Book("金苹果", "描述了苹果种植的过程"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputSteam("c://book.txt")); oos.writeObject(b); oos.close(); // 反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c://book.txt")); Book o = (Book) ois.readObject(); } static class Book implements Serializable { private String name; private String info; public Book() {} public Book(String name, String info) { this.name = name; this.info = info; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setInfo(String info) { this.info = info; } public String getInfo() { return info; } }
4.4.11 try-with-resources
关闭并释放资源:
-
JDK1.7之前:
public static void main(String[] args) { FileReader fr = null; try { fr = new FileReader("c://book.txt"); int c = fr.read(); System.out.println((char) c); } catch (IOException e) { e.printStackTrace(); } finally { try { fr.close(); } catch (Exception e) { e.printStackTrace(); } } }
-
JDK1.7:
FileReader extends InputStreamReader extends Reader implements Closeable extends AutoCloseable { void close() throws Exception; }
=>能在
try()
括号中创建的对象必须实现AutoCloseable
接口,即必然拥有close()
方法public static void main(String[] args) { try (FileReader fr = new FileReader("c://book.txt")) { int c = fr.read(); System.out.println((char) c); } catch (IOException e) { e.printStackTrace(); } }
public static void main(String[] args) { try (CloseDemo d = new CloseDemo()) { } catch (Exception e) { } } static class CloseDemo implements Closeable { @Override public void close() throws IOException { System.out.println("close方法被调用了"); } }
-
JDK9:
public static void main(String[] args) throw FileNotFoundException { FileReader fr = new FileReader("c://book.txt"); PrintWriter pw = new PrintWriter("c://book.txt"); try (fr; pw) { int c = fr.read(); System.out.println((char) c); } catch (IOException e) { e.printStackTrace(); } }