思维导图
一、文件操作
1、java.io.File
- 访问文件或目录的属性(名字、大小、修改时间等)
- long length() 获取长度
- String getName() 获取名字
- boolean canRead() \ canWrite() 是否可读写
- boolean isHidden() // 是否隐藏
可在文件下右击属性,修改是否可写、是否隐藏 - boolean exists() 判断文件是否存在
- 操作文件或目录(创建、删除)
- exists() 判断文件是否存在
- boolean createNewFile() 创建文件
- boolean mkdir() 创建目录
- boolean mkdirs() 创建多级目录
- boolean delete() 删除文件或空目录
删除目录的前提条件是该目录是一个空目录
- 访问目录子项,但不能读写文件数据
- File[] listFiles() 获取当前目录所有子项
- File[] listFiles(FileFilter filter) 获取满足过滤器要求的子项,不满足则忽略
- boolean isFile() 判断当前File是否是文件
- boolean isDirectory() 判断当前File是否是目录
2、java.io.RandomAccessFile
- 基于指针进行读写 (即:在指针当前位置读写字节)
- 创建模式:r 只读模式、rw 读写模式
- 常用方法
- int read() 读取1字节(“低八位”)并返回int,若返回值为-1,则表示读取到了文件末尾
- 返回值类型为int型是因为read读取低八位时,读取的范围为0-255,读取不到-1;
- 返回值类型不为byte型是因为读取时有可能读取到-1,与是否读取到文件末尾无法区分
- void write(int d) 写出1字节,将给定的int值对应二进制的"低八位"写入
- int read(byte[] data) 一次性读取给定字节数组长度的字节量,并存入到该数组中
- void write(byte[] data) 一次性将给定字节数组所有字节写出
- void write(byte[] data, int index, int len)
- byte[] getBytes(String csn) 将当前字符串按照指定字符集–>字节数组
- 推荐使用该方式转换,因为按照系统默认字符集转换会导致跨平台出现乱码问题
- GBK: 国标编码,中文占2字节 (包含全部中文字符)
- gb2312 简单中文 包括6763个汉字
- BIG5 繁体中文 港澳台等用
- UTF-8: 万国码,对Unicode进行编码,变长编码集,英文1字节,中文3字节 (包含全世界所有国家需要用到的字符)
- IS0-8895-1: 欧洲编码集,不支持中文
- String(byte[] bytes, String charsetName) String重载构造方法, 将给定字节数组按照指定字符集–>字符串
- void writeInt(int v)\writeLong(long v)\writeDouble(double v) 读取基本类型数据
- long getFilePointer() 读取指针操作,返回文件开头偏移量(以字节为单位)
- void seek(long pos) 移动到指针指定位置(以字节为单位)
- int read() 读取1字节(“低八位”)并返回int,若返回值为-1,则表示读取到了文件末尾
二、集合
1、List & Set 比较
List | Set | |
---|---|---|
相同点 | java.util.Collection是所有集合的顶级接口 | |
区别 | 可重复 | 不可重复 |
有序 | 无序 |
2、ArrayList& LinkedList比较
ArrayList | LinkedList | |
---|---|---|
内部实现 | 内部由数组实现 | 内部由链表实现 |
效率 | 查询效率更好 | 增删效率更好,首尾增删元素效率最好 |
线程安全 | 非线程安全 |
3、HashSet& TreeSet & LinkedHashSet & BitSet 比较
HashSet | TreeSet | LinkedHashSet | BitSet | |
---|---|---|---|---|
实现 | 基于 HashMap 实现 | 基于TreeMap实现 | 继承自 HashSet,底层是 LinkedHashMap | 底层是 long 数组,用于替代 List |
4、ArrayList
- 内部由数组实现,查询效率更好
- 在执行插入元素时可能要扩容(每次扩容为 1.5 倍),在删除元素时不会减小数组容量,在查找元素时要遍历数组,对于非null的元素采取equals方式寻找
- 是非线程安全的
- ArrayList 随机元素时间复杂度 O(1),插入删除操作需大量移动元素,效率较低
- ArrayList 可以存储 null 值
- 相关方法
- E get(int index) 获取元素
- E set(int index, E e) 设置元素
- void add(int index, E e) 插入元素
- E remove(int index) 删除元素
- List subList(int fromIndex, int toIndex) 获取子集 (对子集操作就是对集合操作)
- 集合–>数组 T[] toArray(T[] a)
- 参数new对象的下标小于集合元素个数,最后数组也可以正常输出
- 两个 ToArray 方法
- Object[] toArray()方法。该方法有可能会抛出 java.lang.ClassCastException 异常
- T[] toArray(T[] a)方法。该方法可以直接将 ArrayList 转换得到的 Array 进行整体向下转 型
- 数组–>集合 Arrays.asList(array)
- 对数组转换的集合进行操作就是对原数组的操作
- 转换后的集合不支持增删元素操作
否则抛异常( java.lang.UnsupportedOperationException) - 可以自行创建一个集合然后做操作
- 所有集合都支持一个参数为Collection的构造方法
作用: 在创建当前集合的同时包含给定集合中的所有元素
5、LinkedList
- 内部由链表实现,增删效率更好,首尾增删元素效率最好
- 插入元素时需要创建一个Entry对象,并切换到相应元素的前后元素的引用;在查找元素时,须遍历链表;在删除元素时,须遍历链表,找到要删除的元素,然后从链表上将此元 素删除即可。
- 是非线程安全的
- LinkedList 的删除、添加操作时间复杂度为 O(1),查找时间复杂度为 O(n),查找函数有一定优化,容器会先判断查找的元素是离头部较近,还是尾部较近,来决定从头部开始遍 历还是尾部开始遍历 。
- LinkedList 实现了 Deque 接口,因此也可以作为栈、队列和双端队列来使用
- LinkedList 可以存储 null 值
6、集合存放元素存放的是元素的引用(地址)
7、集合方法
- int size() 返回当前集合元素个数
- boolean isEmpty() 判断当前集合是否不含任何元素
- boolean add(E e) 添加元素,成功后返回true
因为Set接口添加重复元素会返回false - void clear() 清空集合元素
- boolean contains(E e) 判断当前集合是否包含给定元素
集合判断是否包含指定元素是依靠元素的equals比较的结果。只要集合中有元素与给定元素比较为true,则认为包含。
8、集合之间操作
- boolean addAll(Collection c) 将给定集合添加到当前集合中
- boolean containsAll(Collection c) 判断当前集合是否包含给定集合中所有元素
- boolean removeAll(Collection c) 删除当前集合中与给定元素的交集部分
9、获取元素操作
- 采用迭代器模式,遍历当前集合
- java.util.Iterator
- Iterator接口,定义了迭代器遍历集合的相关操作方法,不同集合都提供了一个迭代器实现类
- Iterator iterator() 该方法用于遍历当前集合的迭代器
- 迭代器要求在遍历集合过程中不能通过集合的方法增删元素,否则会抛出异常
- 遍历集合(问、取、删)
- Iterator iterator = collection.iterator(); 返回Collection元素上进行迭代的迭代器
- 问: boolean hasNext() 判断当前集合是否还有元素可以迭代
- 取: E next() 获取集合下一个元素
- 删: iterator.remove() 删除不是必要操作
迭代器提供的remove方法,删除通过next遍历出来的数组
迭代器要求在遍历集合的过程中不能通过集合的方法增删元素,否则会抛出异常
10、集合排序
- java.util.Collections 集合工具类,定义了很多静态方法,便于操作集合
- Collections.sort(list) 排序
- 不足之处
- 该方法要求集合元素必须实现Comparable接口(其内部重写int compareTo(T o)方法),对代码有侵入性,实际开发中不可取。
- 若元素已经实现类Comparable接口并定义了比较规则,但不满足需求时,该方法无法使用。
- static void sort(List list, Comparator c)
- 该方法要求传入两个比较器,然后根据比较器比较的结果对元素进行排序
- 该方法不需要实现Comparable接口,也不需要元素自身的比较规则
- new Comparator(){}内部需要重写int compare(T o1, T o2)方法。
- 不足之处
- Collections.shuffle(list) 乱序
11、队列
- java.io.Queue接口
- 继承自Collection,具有集合特性
- 先进先出原则
- 常用实现类: java.util.LinkedList
- 相关方法
- boolean offer(E e) 入队操作
- E poll() 出队操作
- E peek() 引用队首元素
- 遍历队列可使用迭代器(新循环),遍历不会影响队列中的元素
- java.io.Deque 双端队列
- 继承自Queue,两端都能做进出队操作
- offer、offerLast 从队尾入队
- offerFirst 从队首入队
- poll、pollFirst 从队首出队
- pollLast 从队尾出队
- Queue 实现类之间的区别
- 非线程安全的:ArrayDeque、LinkedList、PriorityQueue
- 线程安全的:ConcurrentLinkedQueue、ConcurrentLinkedDeque、ArrayBlockingQueue、 LinkedBlockingQueue、PriorityBlockingQueue
12、栈
- 先进后出原则
- java.util.Deque支持栈操作
- void push(E e) 入栈操作
- E pop() 出栈操作
三、泛型
1、泛型介绍
- 又称参数化类型,将原来具体的类型参数化,然后在使用时传入具体的类型,JDK1.5后推出的特性。
- 允许调用者在调用某个类的功能时传入一个或多个类型来定义该类的属性;
- 方法的参数以及返回的类型;
- 提高代码的灵活度。
2、泛型局限性
- 只能使用包装类型,不能使用基本数据类型;
- 运行时类型查询只适用于原始类型,不适用于带类型参数的类型;
- 不能创建带有类型参数的泛型类的数组,只能使用反射来创建泛型数组;
// 不能创建带有类型参数的泛型类的数组 public class TestAwen<T> { // Java的泛型在编译时会擦除类型信息。 // Java中的数组是通过反射动态建立的,没有类型信息不能构造出数组。 // 其实List或ArrayList就是已经做好了的泛型数组。 T [] pairs = new T[100]; //错误 } // 只能使用反射来创建泛型数组 public static T[] minmax(T… a){ T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType(),个数); }
3、通配符
- T、K、V、E 等泛型字母为有类型,类型参数赋予具体的值
- ? 未知类型,类型参数赋予不确定值,任意类型
- ? 只能用在声明类型、方法参数上,不能用在定义泛型类上
// ? 不能定义在泛型类上 public class TestAwen<T> { T name; public static void main(String[] args) { // ? 用在声明类型上 TestAwen<?> awen = new TestAwen<String>(); test(new TestAwen<Integer>()); } // ? 用在声明方法参数上 private static void test(TestAwen<?> testAwen) { System.out.println(testAwen.name); } }
- 对于参数值是未知类型的容器类,只能读取其中元素,不能向其中添加元素。(因为,其类型是未知,所以编译器无法识别添加元素的类型和容器的类型是否兼容,唯一例外的是null。)
- List<?> list; List<? extends ArrayList> list;
- 注意:这里的extends表示指定的类型必须继承某个类或实现先某个接口(不是用implements)
- 如果实现了多个接口,可以用&将接口隔开
- T extends Comparable & Serializable
- List<?> list; List<? extends ArrayList> list;
- ? 只能用在声明类型、方法参数上,不能用在定义泛型类上
- List<String>、List<?>
- List<String>:表示list内的每个元素都是String类型
- List<?> :表示list内的每个元素类型都相同,但具体类型未知
- List<? extends Zoo>、List<? super Dog>
- extends 指定类型必为自身或其子类
- ? 可以传入本身及其子类
- ? 可以传入通配符和extends+本身及子类
- 不能传入只有通配符不含extends+指定类
- super 指定类型必为自身或其父类
- extends 指定类型必为自身或其子类
4、PECS
-
producer-extends, consumer-super
- 要往泛型类读数据时,用 extends;
- 要从泛型类写数据时,用 super;
- 既要取又要写,就不用通配符(即 extends 与 super 都不用)比如 List<Integer>。
-
如果参数化类型表示一个 T 生产者,就是<? extends T>;
-
如果参数化类型表示一个 T 消费者,就使用<? super E>;
-
例1:Stack的pushAll和popAll方法
public void pushAll(Iterable<? extends E> src) { for (E e : src) push(e); } public void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop()); }
-
例2:Collections的copy方法
public static <T> void copy(List<? super T> dest, List<? extends T> src) { int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) { for (int i=0; i<srcSize; i++) dest.set(i, src.get(i)); } else { ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T> si=src.listIterator(); for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); } } }
5、泛型擦除
- 定义:编译器生成的 bytecode 字节编码是不包含泛型信息的,泛型类型信息将在编译处理是被擦除,这个 过程即泛型擦除。
- 擦除类型变量,并替换为限定类型(无限定的变量用 Object)。
- 比如:T extends Comparable,后面所有出现T的地方都会被替换为Comparable;
- 如果调用时指定某个类,比如TAwen<String> awen = new TAwen<>();,则TAwen中所有的T都被替换为String。
- 泛型擦除带来的问题
- 泛型类的静态变量是共享的
- 无法使用具有不同类型参数的泛型进行方法重载
// 第一种 // 编译出错 // Erasure of method test(List<Integer>) is the same as another method in type TestAwen<T> public void test(List ls) { System.out.println("Sting"); } public void test(List li) { System.ut.println("Integer"); } // 第二种 public interface TestAwen<T,U>{ void add(List<T> list1); void add(List<U> list2); }
- 因为 Java 泛型的擦除并不是对所有使用泛型的地方都会擦除的,部分地方会保留泛 型信息,在运行时可以获得类型参数。
四、Map
java.util.Map是接口,提供了根据key查询value的功能。
1、HashMap
- HashMap 散列表/哈希表,是查询“最快”的API,尽量将查找功能用Map优化
- 基于哈希表实现,底层是数组+链表/红黑树,无序键值对集合,非线程安全
- HashMap的key、value都允许为空
- HashMap在遍历的时候,得到的元素顺序不是put的顺序
- 加载因子默认为0.75,创建一个大小为16的数组,在添加到第12个元素以后进行扩容。
- 如果加载因子越大,对空间的利用更充分,但是查找效率会降低(链表长度会越来越长);
- 如果加载因子太小,那么表中的数据将过于稀疏(很多空间还没用,就开始扩容了)
- 基于 key hash 寻找 Entry 对象存放在数组中的位置,对于 hash 冲突采用链表/红黑树的方式来解决。
- 哈希冲突时采用链表法的类,一个哈希桶多于 8 个元素改为 TreeNode static class Node<K,V> implements Map.Entry<K,V>
- 哈希冲突时采用红黑树存储的类,一个哈希桶少于 6 个元素改为 Node static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>
- 某个桶对应的链表过长的话搜索效率低,改为红黑树效率会提高
- HashMap 在插入元素时可能会扩大数组的容量(扩容2倍),在扩大容量时需要重新计算 hash,并复制 对象到新的数组中。
- 扩容机制:使用一个容量更大的数组来代替已有的容量小的数组,transfer方法将原有的Entry数组的元素拷贝到新的Entry数组里。
jdk1.8之前需要重新计算每个元素在数组中的位置;
jdk1.8不是重新计算,而是用了一种更巧妙的方式。
- 扩容机制:使用一个容量更大的数组来代替已有的容量小的数组,transfer方法将原有的Entry数组的元素拷贝到新的Entry数组里。
2、TreeMap
- 采用二叉树算法,相当于二分查找,速度很快
- 有序
- 一般来说,如果需要使用的 Map 中的 key 无序,选择 HashMap;如果要求 key 有序,则 选择 TreeMap。
但是选择 TreeMap 就会有性能问题,因为 TreeMap 的 get 操作的时间复杂度是 O(log(n)) 的,相比于 HashMap 的 O(1)还是差不少的,LinkedHashMap 的出现就是为了平衡这些因 素,使得能够以 O(1)时间复杂度增加查找元素,又能够保证 key 的有序性。
3、Map 常用方法
- V put(K key, V value); 将数据添加到Map
- V get(Object key); 根据key查询value
- int size(); 获取Map集合元素个数
- boolean containKey(Object key); 检测Map中是否包含指定的key
- V remove(Object key); 删除Map集合指定元素,返回值为key对应的value值
- void clear(); 清空Map集合内容
- 注意
- Map中Key不允许重复,Value可以重复
- Map允许添加一个key为null的数据
- Map集合无序
4、Map的遍历
- map没有提供直接遍历的方法,可以利用entrySet和keySet间接实现遍历
- Entry的实现类是HashMap的内部类
- Entry对象代表map中的key-value对,Entry对象包含两个属性(key和value)
- Set<Entry<String,String>> set= map.entrySet(); set中包含map中全部的key-value对,只要遍历了set就相当于遍历了map
5、HashMap工作原理
- HashMap内部利用数组存储数据;
- 根据key的hashCode值计算出数组的下标位置,进行添加或者查询数据;
- 根据hashCode计算出数组下标位置的算法称为”散列算法”;
- 数组下标位置会重复,重复时利用链表存储重复元素,这个链表称为"散列桶";
- 添加和查询时如果有散列桶,则根据equals方法逐个比较找到位置;
- 由于利用hashCode直接定位到数组的存储位置,无需依次查找,所以HashMap具有极高的查找性能。
- 调用hashCode方法计算hash的方法
- key.hashcode()返回一个int值32位
- 计算方式:高16位异或低16位 或者 取余length
- jdk1.7之前是取模运算
- jdk1.8之后是>>>16运算
6、影响HashMap的查找性能因素
-
如果数据多,而数组容量少,大量数据重复的存储在散列桶中,造成在散列桶中进程大量的顺序查找,性能差。
解决方案:提供更多数组容量,减少散列桶中重复数据 -
hashCode方法
Map中的key类型必须实现hashCode方法,否则会影响hashMap的性能,甚至会造成错误。
Java规定:
如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
如果两个对象的hashCode相同,它们并不一定相同。 -
equals方法
HashMap添加或查找时,先根据hashCode计算数组下标位置,然后再利用equals比较key对象是否相同。
注意:如果key的hashCode和equals方法不一致,会造成HashMap工作异常,可能会重复添加或者查找不到数据。
建议成对的重写equals和hashCode方法。 -
创建HashMap的性能优化参数
new HashMap(数组容量, 加载因子)
默认new HashMap()等价于new HashMap(16, 0.75f)
在添加到12个元素(即: 75%)以后进行扩容,扩容2倍
7、HashMap 与 Hashtable 区别
HashMap | Hashtable | |
---|---|---|
默认容量 | 16 | 11 |
扩容策略 | 扩容要求为 2 的整数次幂(*2) | 不要求底层数组的容量一定要为 2 的整数次幂(*2+1) |
是否允许null | key 和 value 都允 许为 null(key 只能有一个为 null,而 value 则可以有多个为 null) | key 和 value 都不允许为 null |
线程安全 | 非线程安全 | 线程安全 |
8、HashMap、TreeMap 与 LinkedHashMap 区别
HashMap | TreeMap | LinkedHashMap | |
---|---|---|---|
底层实现 | 底层基于哈希表,数组+链表/红黑树 | 底层基于红黑树 | 底层基于HashMap和环形双向列表 |
get/put效率 | 最高 | 次之 | 最次 |
遍历取出的数据顺序 | 完全随机 | 按照自然顺序或comparator排序 | 按照插入顺序或访问顺序排序,LinkedHashMap(O(1))的get效率比TreeMapp(O(logn))更高 |
9、HashMap 与 ConcurrentHashMap 区别
HashMap | ConcurrentHashMap | |
---|---|---|
key value是否允许为null | 允许为null | 不允许为null |
线程安全 | 非线程安全 | 线程安全 |
10、ConcurrentHashMap、Collections.synchronizedMap 与 Hashtable 区别
都是同步Map,但是实现同步的机制不相同。
ConcurrentHashMap | Collections.synchronizedMap 与 Hashtable | |
---|---|---|
同步机制 | 基于CAS和synchronized实现的,锁的粒度较小 | 简单地在方法上加synchronized实现的,锁的粒度较大 |
五、IO
IO按读写单位分为字节流和字符流,按方向分为输入流和输出流。
1、输入流与输出流
- 所有字节输入输出流的父类;
- 按照功能分为读写;
- 按照方向分为输入、输出,而方向的参照物为我们写的程序;
- InputStream输入流用来读取数据,OutputStream输出流用来写入数据。
2、节点流、处理流
- 节点流
- 又称低级流;
- 是实际连接程序与另一端的"管道",负责实际搬运过程;
- 读写建立在低级流的基础之上进行。
- 处理流
- 又称高级流;
- 不能独立存在,需建立在其他流上,使数据"流经"该流时进行加工处理;
- 简化读写操作。
3、文件流
- 文件流
- 是一对低级流,用于读写文件数据
- 功能与RAF一样,但底层读写方式不同
- 流是顺序读写,RAF是基于指针读写
- RAF可以覆盖原有部分数据随机读写,FOS可以重写数据和末尾追加写
- FileOutputStream(File file) 覆盖写(原有文件数据清除) FileOutputStream(String path)
- FileOutputStream(File file, boolean append) 参数为true时追加写(原有文件数据保留)
FileOutputStream(String path, boolean append) - FileInputStream(File file) 文件输入流
FileInputStream(String path) - String(byte bytes[], int offset, int length, String charsetName) 读取bytes数组从offset到length长度的字符串
4、缓冲流
- 缓冲流:是一对高级流,用来加快读写效率,其内部维护了一个字节数组,不用关注是否用块读写加快效率。
- BufferedOutputStream
void flush() 强行将缓冲区一次性写出 - BufferedInputStream
5、对象流
- 对象流
- 是一对高级流,读写Java中的对象
- 当一个类的实例被对象流进行读写时,该类必须实现Serializable接口,类内部需定义常量serialVersionUID(序列化版本号)
- 当一个属性被transient修饰后,在进行序列化时,该属性值会被忽略
- ObjectOutputStream
- void writeObject(Object obj) 将给定对象转换为一组字节后输出
- 对象序列化: obj对象流经对象输出流,将该对象转化为一组字节
- 数据持久化: 转换的这组字节流经文件输出流,写入文件(磁盘)中保存,将数据写入磁盘长久保存
- void writeObject(Object obj) 将给定对象转换为一组字节后输出
- ObjectInputStream
- Object readObject() 将一组字节还原为对应的对象 (前提是这组字节应当是对象输出流将一个对象转换的字节)
- 用于进行对象反序列化操作
- 注: 对象流读取的字节必须是由对象输出流将一个对象序列化后的字节,否则进行反序列化会抛出异常
- Object readObject() 将一组字节还原为对应的对象 (前提是这组字节应当是对象输出流将一个对象转换的字节)
- Serializable接口
- 当一个类的实例要被对象流进行读写时,要求该类必须实现Serializable接口 (该接口里什么也没有,编译器会识别)
- 当一个类实现了Serializable接口后,应当定义一个常量:serialVersionUID (序列化版本号)
- 若不指定序列化版本号,编译器会在编译时按照当前类的结构生成一个版本号;但是若此类的结构发生改变,版本号会跟着改变
- 序列化版本号直接影响对象输入流进行反序列化是否能够成功
- 当反序列化的对象对当前类版本号一致,则反序列化成功,否则失败
- 若当前类结构发生了变化,只要版本号没有改变,则反序列化时会将仍然有的属性还原
- transient
- 当一个属性被transient修饰后,在进行序列化时,该属性值会被忽略
- 忽略不必要的属性可以达到对象序列化的"瘦身"操作
6、字符流
- 字符流
- java将流按读写单位分为字节流和字符流
- 字符流只能用于读写文本数据
- 非文本数据不能使用字符流读取 (如:图片、基本数据类型、MP3等数据)
- java.io.Reader所有字符输入流的父类
- java.io.Writer所有字符输出流的父类
7、转换流
- 转换流
- 是一对高级流,常用的字符流实现类
- 在读取文本数据时,使用高级流进行流连接起到承上启下的作用
- 因为几乎所有的字符流都只能连接在其他字符流上,而基本上低级流都是字节流,由于转换流可以连接字节流,而本身其自身又是字节流,所以起到将字符流与字节流"对接"的作用。
- java.io.InputStreamReader
- int read() 读取一个字符,“低十六位”
- int read(char[] data) 读取一组字符
- java.io.OutputStreamReader
8、缓冲字符流
- 内部有缓冲区,可以块读写,可按行读写字符串
- java.io.BufferedWriter
- PrintWriter(File file, boolean autoFlush) 自动行刷新的缓冲字符输出流
- 内部总是会连接BufferedWriter作为缓冲操作
- println() 自动flush(当autoFlush为true时)
- 调用print()不会自动行刷新
- PrintWriter(File file, boolean autoFlush) 自动行刷新的缓冲字符输出流
- java.io.BufferedReader
- String readLine() 按行读取字符串,若返回值为null表示流末尾
- 读取一行字符串的方法,该方法会连续读取若干字符,当读取到换行符时,将之前读取的字符以字符串形式返回,若返回值为null,表示流的末尾
- String readLine() 按行读取字符串,若返回值为null表示流末尾
9、字节流与字符流比较
字节流 | 字符流 | ||
---|---|---|---|
相同点 | InputStream,OutputStream,Reader,writer都是抽象类 | ||
区别 | 是否使用缓冲区 | 本身不使用缓冲区,与文件本身直接操作 | 使用缓冲区 |
不使用close | 不使用close也能输出内容 | 不使用close不会输出内容,需要flush强制刷新缓冲区才可以 | |
read()返回值范围 | 一个字节(0~255) | 两个字节(0~65535) | |
inputStream的read()方法返回 0 到 255 范围内的 int 字节值,如果已到达流的末尾,则返回 -1。对于不能用0-255来表示的值就得用字符流来读取,比如说汉字 | Reader类的read()方法返回 0 到 65535 之间 (0x00-0xffff),如果已到达流的末尾,则返回 -1。 | ||
处理方式 | 处理字节和字节数组或二进制 | 处理字符和字符数组或字符串 | |
使用范围 | 音频文件、图片、歌曲,所有的硬盘上保存文件或进行传输的时候,操作字节和字节数组或二进制对象。(如果是实现拷贝功能,使用字节流进行操作,采用边读边写方式,节省内存。) | 中文,字符是只有在内存中才会形成的,操作字符、字符数组或字符串。 |
六、异常
处理异常的两种方式:
使用try-catch处理该异常;
在当前方法上声明throws将该异常抛出。
1、try-catch-finally
- try
- try中出错代码之后的内容不会被执行
- try代码不报错不执行catch语句块
- catch
- catch可以定义多个,针对不同异常分别捕获
- 习惯: 在最后一个catch中捕获Exception (避免因为一个未捕获的异常导致程序中断)
- 捕获顺序应当是子类型异常在上面先捕获,父类型异常在下面后捕获
- finally
-
可以直接跟在try后面或者最后一个catch之后
-
finally块是异常捕获机制中的最后一块
-
无论try是否抛出异常,finally中的代码都会执行
-
无论try和catch中有没有return,finally代码都会执行
-
如果在执行try和catch时,JVM退出(比如System.exit(0)),那finally不会执行
-
finally是在return后面的表达式运算后执行的,无论finally中的代码是什么,try中的return返回值不会改变
try语句里有return,执行代码顺序:
1、如果有返回值,就把返回值保存到局部变量中
2、执行jsr指令跳到finally语句里执行
3、执行完finally语句后,返回之前保存在局部变量表里的值 -
如果try和finally里都有return,会忽略try的return,使用finally的return
-
如果try中抛异常,finally中也抛异常,那么原有的异常信息会丢失,只能抛出finally内的异常
-
异常处理机制在IO流中的使用
FileInputStream fis = null;
try {
fis = new FileInputStream("fis.txt");
int d = fis.read();
System.out.println(d);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fis!=null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
JDK1.7后推出一个新特性: 自动关闭
try(
/*
* 实现了AutoCloseable接口的可以定义在这里,该特性是编译器认可,
* 最终编译器还是会将代码改为在finally中关闭该流。
*/
FileInputStream fis = new FileInputStream("fis.txt")
){
int d = fis.read();
System.out.println(d);
}catch(IOException e){
System.out.println("程序异常");
e.printStackTrace();
}
2、throw & throws
如果在一个方法中使用throw抛出异常时,应该在当前方法上使用throws声明该异常抛出,通知调用者在调用当前方法时要处理异常(RuntimeException除外),否则编译不通过。
- throw和throws的区别
- throw:在程序中明确抛出异常
- throws:用来声明方法不能处理的异常
- 当调用一个含有throws声明异常抛出的方法时,编译器要求必须处理这个异常
- 处理方式有两种
在当前方法上继续声明throws将该异常抛出;
使用try-catch-finally处理该异常。
- 处理方式有两种
- throws重写规则
- 可以不再抛出任何异常
- 可以抛出父类方法抛出异常的子类型异常
- 可以仅抛出父类方法抛出的部分异常
- 不允许抛出额外异常
- 不允许抛出父类方法抛出异常的父类型异常
3、异常处理过程
- 当JVM执行代码遇到异常时,会实例化该异常的一个实例,然后设置好代码执行的过程,并将该异常抛出;
- 如果抛出异常的代码所在的方法没有处理异常的能力,异常会抛到当前方法之外, 由调用当前方法的代码片段去处理。
4、Throwable
Throwable是Java中所有错误Error和异常Exception的超类。
- Error:程序无法处理的,如OutOfMemoryError、ThreadDeath等,把它交给jvm处理,jvm在大多数情况下会选择终止线程。
- Exception:程序可以处理的异常,分为两种,可检测异常和非检测异常。
5、Java异常
- 可检测异常
- 需要声明,且必须捕获异常
- 编译期异常
- 常见异常:IO异常、SQL异常等
- 非检测异常
- 不需要声明
- 运行期异常
- RuntimeException属于非检测异常
- 常见异常
- NullPointerException 空指针异常(试图访问空对象的变量、方法或空数组的元素)
- ArrayIndexOutOfBoundsException 数组下标越界异常
- ClassCastException 类型转换异常(试图将对象强制转换为不是实例的子类时,抛出异常)
- IllegalArgumentException 方法的参数无效
- NumberFormatException 数字格式化异常(字符串转换为数字时发生异常,继承IllegalArgumentException)
int i= Integer.parseInt(“abc”); - SecurityException 安全异常
6、异常常用方法
- void printStackTrace() 输出错误堆栈信息,有助于定位错误并解决
- e.getMessage() 获取错误信息
七、线程
1、创建线程的两种方式
- 继承Thread并重写run方法
- 缺点
- java单继承
在实际开发中需要继承某个父类方法,且当前类需要并发运行,导致不能同时又继承复用方法的类又继承线程 - 定义线程的同时重写run方法定义任务
线程和任务有一个必然耦合关系,不利于线程重用
- java单继承
- 缺点
- 实现Runnable接口并重写run方法
2、start
- 线程启动要调用start(),而不是直接调用run()
- start将线程纳入线程调度中,线程调度会统一管理需要并发运行的多个线程,调度CPU,分配时间片段给线程,得到时间片段的线程会被CPU运行这段时间,运行完毕后,线程调度会再分配时间片段给一个线程使CPU运行该线程。
- 线程调度分配时间片段给每个线程并非有序的"一人一次",但是在整体过程中,每个线程获取CPU时间片段的次数基本一致。
3、获取线程相关信息方法
- String getName() 获取线程名字
- long getId() 获取唯一标识
- int getPriority() 获取优先级
- boolean isAlive() 是否活着
- final boolean isDaemon() 是否为守护线程
- boolean isInterrupted() 是否被中断
4、常用方法
- static Thread currentThread() 获取运行该方法的线程
- final void setPriority(int newPriority) 设置线程优先级
- 线程的优先级由数字1-10表示,1是最低优先级,10是最高优先级,5为默认值
- 调整线程优先级可以最大程度的改善某个线程获取CPU时间片的次数
- 理论上线程优先级越高的线程获取CPU时间片的次数越多
- static void sleep(long millis) 阻塞状态 指定毫秒超时后重新回到RUNNABLE状态,等待分配CPU时间片再次运行
- void interrupt() 中断正在运行的线程
- 不是将线程直接中断,而是中断其阻塞状态,这时会抛出中断异常,通知程序该线程的阻塞状态被打断
- final void setDaemon(boolean on) 守护线程(后台线程)
- 一个线程创建出来默认都是普通(前台)线程,守护线程需要在线程启动前单独进行设置(即该方法必须在start之前调用)
- 使用上守护线程与普通线程无差别,但是在结束时机上有一点不同 (当进程结束时,所有正在运行的守护线程都会被强制中断)
进程结束: 当一个进程中的所有前台线程都结束时,进程即结束。
- final void join() 协调线程之间的同步运行
- 让运行该方法的线程处于阻塞状态,直到该方法所属线程运行完毕才会解除阻塞
- void yield() 暂停当前正在执行的线程并执行其他线程 (让出CPU,线程切换)
5、synchronized
- 同步方法: 被synchronized修饰的方法
- 多个线程不能同时在方法内部执行,解决并发问题
- 在方法上使用synchronized,同步监视器对象就是当前方法所属对象 (即: 方法内部看到的this)
- 同步块可以更准确控制需要,同步监视器是java中任意的一个对象,只要保证多个线程看到的该对象是“同一个”, 即可保证同步块中的代码是并发安全的
- 静态方法使用synchronized修饰后,则该方法具有同步效果,与对象无关 (静态方法一般用类名点来访问,当然静态方法不同的对象调用也具有同步效果)
- 互斥锁:当使用synchronized锁住多段不同的代码片段,但是这些同步块使用的同步监视器对象是同一个时,那么这些代码片段之间就是互斥的,多个线程不能同时执行他们
- 死锁:当多个线程都持有自己的锁,但是都等对方先释放锁时就会出现“僵持”的情况,使得所有线程进入阻塞状态,这个现象称为死锁
6、线程池
- 作用: 控制线程数量、重用线程
- 控制线程数量: 每个线程都会占用进程一部分内存,线程数量过多会导致资源消耗大,由于所有线程都是并发运行,所以过多线程会导致CPU过度切换,导致并发效率变差
- 重用线程: 频繁创建销毁线程会给线程调度带来负担,应当重用线程
- 创建步骤
Step1: 创建固定大小线程池
ExecutorService threadPool = Executors.newFixedThreadPool(nThread);
Step2: 创建线程任务
Runnable r = new Runnable(){ public void run(){...} };
Step3: 将任务交给线程池处理
threadPool.execute(r);
停止线程池
threadPool.shutdown();
等任务执行完后停止
threadPool.shutdownNow();
试图立即停止任务
7、获取IP地址
- Window:ipconfig
- Linux:/sbin/ipconfig
8、线程图
9、ThreadLocal
- 是线程内部的数据存储类,通过它可以在指定的线程中存储数据,对数据存储后,只有在线程中才可以获取到存储的数据,对于其他线程是无法获取到数据的。
- 使用场景
- 当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候使用。
- 复杂逻辑下的对象传递。
比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性
- 原理
- 通过ThreadLocal.set()设置线程中的对象是该线程自己使用的对象,其他线程不需要访问也访问不到,各个线程中访问的是不同的对象.
- 各个线程独立的对象是在每个线程new的对象,而不是通过ThreadLocal.set()创建的。
- ThreadLocal.set() 将新建对象的引用保存到线程独有的map中,当有其他线程对这个引用指向的对象做修改时,当前线程Thread对象中保存的值也会发生变化。
- 当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal.set() 源码public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
八、反射
1、定义
在运行状态中,动态获取类的信息以及操作类的方法和属性,称为反射。
2、意义
优点:反射机制可以对一个类的加载、实例化、调用方法操作属性从编码期改为运行期运行,大大提高代码灵活度。
缺点:但运行期进行反射操作会消耗额外的资源与性能,降低执行效率,需适度使用。
3、JVM加载类的方式
- Test test = new Test()
- 执行代码时需要用到某个类
- JVM会加载Test.class
- Class.forName(String className)
- 通过反射机制中的方法,以字符串形式告知JVM加载指定的类
- 此方法只能寻找环境变量中配置的类路径中指定的类
- ClassLoader.loadClass
- 通过类加载器来加载指定的类
- 类加载器可以额外指定环境变量中没有指定的类路径,从中寻找指定的类进行加载
Class.forName | ClassLoader.loadClass | |
---|---|---|
何时执行代码块 | 在类加载的时候会执行静态代码块 | 在调用newInstance方法的时候会执行静态代码块 |
初始化不同 | 会对类初始化 | 只会装载或链接 |
类加载器 | Class.forName使用调用forName方法的类的类加载器来加载;也有一个重载的方法,可以指定加载器。 | ClassLoader.loadClass是一个实例方法,调用时需要自己指定类加载器,这个类加载器可能是也可能不是加载调用代码的类加载器。 |
4、作用
- 动态加载类、动态获取类的信息(类名、属性、构造器、方法)
- 动态创建对象、调用对象方法、调用构造器
- 动态处理属性
- 获取泛型信息
- 处理注解
5、加载类过程
- 加载类过程是JVM读取该类对应的class文件
- 过程
当JVM读取类的class文件后,会实例化Class类的实例用于保存加载这个类的信息;
并且每个被加载的类只会进行一次加载过程,意味着每个被JVM加载的类都会在JVM内部以Class类的实例进行保存,所以每个类有且只有一个Class类的实例与之对应。 - Class也称为类对象,通过Class可以获取其表示的类的相关信息,如: 类名、构造器、属性、方法,还可以实例化其表示的类。
6、方法
Class.forName(String className)
可以传入的类只要在类路径下的类都可以,也可以时API中Java提供的类。Object newInstance()
该方法是调用当前Class所表示的类的无参构造方法,实例化一个该类实例并将其返回。
注意: 该类必须有无参构造才可以使用这个方法。Method getDeclaredMethod(String name, Class[] args)
获取当前Class所表示的类定义的指定名字及参数列表的方法
通过Method可以获取到其表示的方法的相关信息,如: 方法名、返回值类型、参数类型、访问修饰符等;也可以通过Method动态调用其表示的方法。Object invoke(Object obj, Object[] args)
参数1: 传入该方法所属对象
参数2: 实际参数,若该方法无参则传入nullvoid setAccessible(true)
调用私有方法,需要设置访问权限,否则会报异常。
java.lang.IllegalAccessException: class reflect.Person with modifiers “private”
好处:可以提高程序的执行效率