java.util.Collection
java集合是存储对象的工具类,有两个常见子接口
- Set: 不可重复集,重复元素不能放入集合两次以上, 顺序不定
- List: 可重复集:ArrayList(),LinkedList(),顺序固定
元素是否重复是依靠元素自身equals比较的结果而定
集合中存储的都是引用类型对象。且只保存每个元素对象的引用(即地址),并非将元素对象本身存入集合,基本类型装箱放入。
基本类型也可以放入集合,会自动拆装箱转为引用类型。
Collection集合接口所共有的方法:
- int size() 集合元素个数
- boolean isEmpty() 是否不含有任何元素
- void clear() 清空集合
- boolean add(E e):向集合中加元素,成功返回true
- boolean addAll(Collection c):将一个集合所有元素添加到当前集合,有添加返回true
- boolean contains(E e) 集合是否包含指定元素,同样根据equals比较结果
- boolean containsAll(Collection c) 判断集合是否包含给定集合中所有元素
- boolean remove(E e) 删除元素,根据元素equals比较的结果进行删除
- void removeAll(Collection c) 删除c中有的元素
- boolean retainAll(Collection<?> c) 原始集合变为交集内容,方法参数中集合不变。没有交集则为空集合
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,3));
List<Integer> list2 = new ArrayList<>(Arrays.asList(3));
list.retainAll(list2);
// [3, 3]
System.out.println(list);
// [3]
System.out.println(list2);
List<Integer> list3 = new ArrayList<>(Arrays.asList(1,2,3));
List<Integer> list4 = new ArrayList<>(Arrays.asList(4));
list3.retainAll(list4);
// []
System.out.println(list3);
// [4]
System.out.println(list4);
子接口List
List常被称为可变数组
实现类
- ArrayList:通过动态数组实现List接口,更适合于读取数据
- LinkedList:通过链表实现List接口,更适合于插入和删除,链表结构
特有方法,多与操作下标有关
- String str = list.get(1) 获取给定下标处对应的元素
- String old = list.set(2, “三”) 将给定元素设置到指定位置,返回值为原位置对应的元素
- list.add(1,“2”) 将给定元素插入到指定位置
- String old = list.remove(2) 将给定位置的元素从集合中删除并将该元素
- List sublist(int startindex, int endindex) 截取List中下标从startindex 到endindex-1的元素,返回子集subList,截取的时候含头不含尾,subList与原List占有相同的存储空间,对subList的操作会影响原List,例如:
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,3));
List<Integer> subList = list.subList(0, 2);
subList.set(0, 2);
// [2, 2, 3, 3]
System.out.println(list);
// [2, 2]
System.out.println(subList);
subList.clear();
// [3, 3]
System.out.println(list);
// []
System.out.println(subList);
数组和List集合之间的相互转换
集合转数组,Collection接口提供了方法
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// JDK1.6以后传入一个长度为0的数组即可
Integer[] integers = list.toArray(new Integer[0]);
数组转集合,使用数组的工具类Arrays
int[] ints = new int[]{1,2};
List list = Arrays.asList(ints);
注意:对于转化而来的list不能进行add() / remove() / clear()操作。否则会抛出异常:UnsupportedOperationException。因为转化而来的是李鬼:ArrayList的内部类,非我们常见的ArrayList。如果需要操作,则需要用李逵包裹一下,如:
int[] ints = new int[]{1,2};
List list = Arrays.asList(ints);
List realList = new ArrayList<>(list);
// 正常运行
realList.clear();
队列与栈
LinkedList实现了Deque接口,Deque同时具有队列和栈的性质。因此在这里用LinkedList做示例。
队列
队列存取元素遵循先进先出原则。first in first out(FIFO)
- boolean offer(E e):进队列
- E poll():出队列
- E peek():引用队首元素
LinkedList<Integer> list = new LinkedList<>();
list.offer(1);
list.offer(2);
list.offer(3);
// 1
System.out.println(list.poll());
// [2, 3]
System.out.println(list);
// 2
System.out.println(list.peek());
// [2, 3]
System.out.println(list);
双端队列,两端都可进出
- deque.offerFirst(“three”) 队头进队
- deque.offerLast(“four”) 队尾进队
- str = deque.pollFirst() 队头出队
- str = deque.pollLast() 队尾出队
LinkedList<Integer> list = new LinkedList<>(Arrays.asList(1,2,3));
// true
System.out.println(list.offerFirst(4));
// [4, 1, 2, 3]
System.out.println(list);
// true
System.out.println(list.offerLast(5));
// [4, 1, 2, 3, 5]
System.out.println(list);
// 4
System.out.println(list.pollFirst());
// [1, 2, 3, 5]
System.out.println(list);
// 5
System.out.println(list.pollLast());
// [1, 2, 3]
System.out.println(list);
栈
栈存取元素遵循先进后出原则。first in last out(FILO)
- stack.push(“one”) 入栈
- String str = stack.pop() 出栈
LinkedList<Integer> list = new LinkedList<>(Arrays.asList(1,2,3));
list.push(1);
list.push(2);
// 2
System.out.println(list.pop());
// 1
System.out.println(list.pop());
Iterator
Iterator即迭代器,是一个接口提供了统一的遍历集合元素的方式,在使用迭代器遍历集合的过程中,不能通过集合的方法修改集合元素,否则会抛出异常
常用方法:
- boolean hasNext() :通过迭代器询问集合是否还有元素
- E next():从集合中取出下一个元素
- remove():在原集合中删除当前元素。在调用remove()之前,必须通过迭代器的next()方法迭代过一次,并且不能再次调用remove()
List<String> list = new ArrayList<>();
list.add("abc");
list.add("edf");
list.add("ghi");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s =it.next();
System.out.println(s);
it.remove();
}
// []
System.out.println(list);
java8新增forEach
List<String> list = new ArrayList<>();
list.add("abc");
list.add("edf");
list.add("ghi");
list.forEach(x -> {
System.out.println(x);
});
Map
以key-value对的形式存储元素,key在Map中不允许重复,Map查询速度最高,java中无对象能及。
常用的实现类:
- HashMap
- TreeMap
- LinkedHashMap
常用方法
- V put(K key, V value)
map.put("语文", 99);
存入:返回值为Null
替换:当key值已存在,返回值为被替换掉的value
- V get(Object key) 返回参数key所对应的value对象。
d = map.get("语文");
- V remove(K k) 删除Key值为k的存储对,并返回value值。
d = map.remove("数学");
- boolean containsKey(K k) 是否包含指定Key值
- boolean containsValue(V v) 是否包含指定value
- void clear() 清空map
- boolean isEmpty() map是否没有元素
- void putAll(Map<? extends K,? extends V> m) 将另一个map添加到当前Map
- int size() map元素个数
以下为1.8以后新增的一些方法,由于这些方法我也是初次使用,如有错误,请指正。在了解这些方法之前要懂得两个单词:present 的意思是map.containsKey(key) && map.get(key) != null
,
absent意思是:!map.containsKey(key)
- default V putIfAbsent(K key, V value) 当key值absent时,放入value
Map<String,String> map = new HashMap<>();
map.put("one", "old");
map.put("two", null);
map.putIfAbsent("one", "new");
// {one=old, two=null} new没有放进去因为one present
System.out.println(map);
map.putIfAbsent("two", "old");
// {one=old, two=old} two放进去了,因为two absent
System.out.println(map);
// {one=old, two=old, three=old}
map.putIfAbsent("three", "old");
System.out.println(map);
Map遍历方法
- 遍历所有的key
/*
* 遍历所有的key:
* Set<K> keySet()
* 将当前Map中所有的key以一个Set集合形式返回
* 所以遍历这个Set集合就等同于遍历了所有的key
*/
Set<String> keySet = map.keySet();
for(String key : keySet){
System.out.println("key:"+key);
}
- 遍历每一组键值对(Entry)
/*
* 获取每一组键值对
* 在Map内部,每一组键值对是用Map内部类Entry
* 的实例表示的(Entry是接口,不同的Map实现类
* 都实现了Entry用于表示一组键值对)
* Set<Entry> entrySet()
* 将当前Map中所有的键值对(若干Entry实例)存入
* 一个Set集合并返回。
*/
Set<Entry<String,Integer>> entrySet = map.entrySet();
for(Entry<String,Integer> e : entrySet){
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key+":"+value);
}
- 遍历所有的value
/*
* 遍历所有value
* Collection<V> values()
* 将当前Map中所有的value存入一个集合后返回
*/
Collection<Integer> values = map.values();
for(Integer value : values){
System.out.println("value:"+value);
}
- 1.8新增遍历方法
Map<String,String> map = new HashMap<>();
map.put("one", "1");
map.put("two", "2");
map.put("three", "3");
map.forEach((k,v) -> {
System.out.println("key:" + k + ",value:"+v);
});
HashMap存储数据的过程
- 获取key的hashCode值
- 根据hashCode值,通过hash算法,计算键值对的存放位置。将键值对以Entry实例存入,存放方式为链表
- 若hashCode值与已存key的hashCode值相同,则调用equals方法比较两个key是否相同,如果相同,则是value替换操作。如果不同,则存在相同的位置,与上一个元素形成链表结构。会降低HashMap的检索性能,应当避免。
ArrayList与HashMap初始大小与扩容
ArrayList
- 在第一次add的时候,分配10容量
- 扩容计算方式:oldCapacitity × 3 ÷ 2 +1 = 16
- 扩容使用Array.copyOf()
HashMap
- 初始存储容量16
- 扩容算法:扩容翻倍,即第一次扩容后为:16 * 2 = 32
- 但是实际放入元素的个数threshold = 存储大小 * 负载因子Load Fator = 16 * 0.75 = 12
hashCode与equals
- hashCode值是根据对象的地址进行相关计算得到的。
- equals通常用来判断两个对象是否相等;如果是判断两个对象是不是同一个对象应该用
=
两者关系
一般情况下两者没有半毛钱关系,但是如果你的Map的key用的是自定义对象那必须同时重写hashCode和equals,用IDE自动生成的就可以了。一般情况下,都是用String作为key,因为String类重了,看下源码:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
Object类对于hashCode和equals有如下要求:
- 如果两个对象的equals的结果是相等的,那么两个对象的hashCode值返回的结果也必须是相同的
- 任何时候重写equals,都必须同时重写hashCode
Collections.sort
sort方法用于对List集合进行自然排序(升序),即1,2,3,a,b,c的形式。
List<String> list = new ArrayList();
list.add("1");
list.add("3");
list.add("2");
list.add("bb");
list.add("b");
list.add("ab");
Collections.sort(list);
// [1, 2, 3, ab, b, bb]
System.out.println(list);
当集合内的元素是自定义对象时,我们常常会自定义排序规则,其有两种实现方式
Comparable接口
实现Comparable接口,并重写compareTo()来定义比较规则,
- 当返回值 > 0,表示当前对象 > 参数对象
- 当返回值 < 0,表示当前对象 < 参数对象
- 当返回值 = 0,表示当前对象 = 参数对象
public class Student implements Comparable<Student>{
public int id;
public String name; public int age;
public Student(){}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;}
@Override
public String toString() {
return " [id=" + id + ", name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Student s) {
return this.age-s.age;
//此时为升序排序 return s.age-this.age; //此时为降序 排序
}
}
List<Student> list=new ArrayList<Student>();
list.add(new Student(1, "赵一", 10));
list.add(new Student(2,"钱二", 35));
list.add(new Student(4, "李四", 20));
Collections.sort(list);
System.out.println(list);
Comparator
当不想修改原始类型时使用
public void test5(){
List<Student> list=new ArrayList<Student>();
list.add(new Student(1, "赵一", 10));
list.add(new Student(2,"钱二", 35));
list.add(new Student(4, "李四", 20));
list.add(2,new Student(3, "孙三", 25) );
Collections.sort(list, new Comparator<Student>(){
@Override
public int compare(Student s1, Student s2) {
return s1.getAge()-s2.getAge();
}
});
System.out.println(list);
}
java.io.File
File用于表示文件系统中的一个文件 / 目录 ,使用File可以
- 访问该文件或目录的属性信息(名字,大小,修改日期等)
- 操作文件或目录(创建,删除)
- 若表示的是目录,可以查看该目录中的子项信息,但是不能访问一个文件中的数据
构造方法
- File(String pathname) 通过路径构造一个文件
// 获取当前文件夹下的demo.txt
File file = new File("."+File.separator+"demo.txt");
File.separator分隔符,用来屏蔽不同系统之间的差异。
常用方法
- boolean isFile()
- boolean isDirectory()
- boolean exist()
- String getName() 获取文件或目录名字
- long length() 返回当前File对象所表示的文件占用的字节量,目录或不存在的文件返回0
- boolean delete() 当且仅当成功删除文件或目录时,返回true,否则返回false。当File对象所表示的是一个目录时,需保证此为空目录才可成功删除
针对文件
- boolean creatNewFile():若指定的文件不存在,并成功创建则返回true,否则返回false
针对目录
- boolean mkdir() 创建目录,该目录的上级目录存在才会创建目录。
- boolean mkdirs() 创建目录,该目录的上级目录不存在会一并创建
- File[] listFiles() 将当前目录所有子项存入一个File数组,若子项为空,则数组也为空。如果抽象路径名表示的不是一个目录,或者发生I/O错误,则返回null
File file = new File("./demo.txt");
// null
System.out.println(file.listFiles());
- File[] listFiles(FileFilter filter) 将当前目录所有子项经过过滤存入一个File数组
例如:过滤子项,只显示目录,看下目录结构
File file = new File("C:/dic");
File[] files = file.listFiles(f -> f.isDirectory());
// [C:\dic\dic1, C:\dic\dic2]
System.out.println(Arrays.toString(files));
常见面试题之递归删除目录
public static void delete(File file){
if (file.isDirectory()) {
//先将该目录清空
File[] subs = file.listFiles();
for(File sub : subs){
delete(sub);//递归调用
}
}
file.delete();
}
IO流
流的一种分类方式:
- 输出流(output) 用来写出数据
- 输入流(input) 用来读取数据
另一种分类方式:
- 低级流:是真实负责读写的流,直接连接数据源,负责将数据搬运。特点:数据源明确,知道数据从哪里来,或者数据写到哪里去
- 高级流:高级流基于低级流,提供的额外功能简化读写数据的操作。
OutputStream
低级流,抽象类,所有字节输出流的父类
- void write(int b)
将参数 int作为byte写入文件中,写的是int的低8位,高的24位舍弃 - void write(byte[] d):写一个字节数组
- (子类)FileOutputStream
构造方法 - new FileOutputStream(File file)
- newFileOutputStream(String str)
- 以上两个构造方法都可以添加第二个参数true or false,若为true,则写入的数据都是追加在文件末尾
- 打开文件,如果不存在,会自动创建一个文件。如果没有权限创建文件,则 抛出异常。如果文件已经存在,文件将被删除,重新换掉
- (子类)FileOutputStream
InputStream:是所有字节输入流的父类。其是抽象类
方法
- int read():读低8位
- int ready(byte[] d)
- (子类)FileInputStream
构造方法- new FileInputStream(File file)
- newFileInputStream(String str)
- 如果文件存在,打开文件,指针在0位置; 如果文件不存在,抛出异常
- 如果文件不允许读,抛出异常
##缓冲流
缓冲流:
- BufferedInputStream:缓冲输入流,提高读取效率
- BufferedOutputStream:缓冲输出流,提高写出效率
缓冲流内部维护着一个缓冲区(字节数组)
bis.read()看似读取一个字节,实际上缓冲流会一次性通过fis读取一组字节,并存入内部维护的字节数组中,然后将第一个字节返回。这样当再次调用bis.read()读取一个字节时,会直接从内部的字节数组将第二个字节返回。
所以缓冲流还是通过提高一次实际读取的字节量,减少实际读取次数提高的效率
缓冲输出流也是类似原理。
- 缓冲区内部字节数组的长度:8192
class MyBis extends BufferedInputStream{
public MyBis(FileInputStream fis){
super(fis);
System.out.println(super.buf.length);
}
}
@Test
public void test7() throws FileNotFoundException{
MyBis mb=new MyBis(new FileInputStream("demo.txt"));
//输出8192
}
- 通过缓冲流复制文件
@Tes
public void test6() throws IOException{
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("demo.txt"));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("demo1.txt"));
int len; //读到的字节数
byte[] bytes=new byte[1024];
while((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
bos.flush(); //void flush()一次性将缓冲区中的数据写出
}
##对象流
如果一个类的实例希望被对象流进行读写,那么该类必须实现Serializable接口
被transient和static修饰的属性在对象序列化时其值会被忽略
将可有可无的属性忽略可以达到对象序列化“瘦身”的效果
当一个类实现了序列化接口后,就应当定义一个
常量:serialVersionUID,序列化版本号
序列化版本号决定反序列化操作是否成功。
当对象输入流在将一组字节进行反序列化时,会对该对象与其对应的类型进行版本号比较,若一致则则反序列,若不一致则抛出版本号不一致的异常
-
百度百科:序列化
**序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。**在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。 -
对象序列化
对象序列化就是将一个java中的对象按照其结构
转化唯一组字节的过程(对象输出流完成) -
持久化
将一组字节写入文件(硬盘上)的过程称为持久化
对象流是一对高级流,作用是方便读写java中的对象。
java.io.ObjectOutputStream
对象输出流,将给定的对象转换为一组字节,然后通过其处理的流将字节写出
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("person.txt"));
oos.writeObject(p);
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("person.txt"));
Person p=(Person)ois.readObject();
##字符流
字符流
字符流与字节流的区别在于读写单位为:字符。但是字符流底层本质还是读写字节,只是字符与字节的转换工作交给了字符流来完成
Reader是所有字符输入流的父类,规定了读取字符的相关方法
Writer是所有字符输出流的父类,规定了写出字符的相关方法
转换流:
InputStreamReader(),OutputStreamWriter:他们是字符流的一对常见实现类
java.io.PrintWriter
缓冲字符输出流
特点:可以按行写出字符串,由于有缓冲,写出字符串效率高
实际上PrintWriter自身的最大特点是支持“自动行刷新”功能
而缓冲功能是靠其内嵌的BufferedWriter实现的。因为实例化
PrintWriter时,它总会内部实例化BufferedWriter并与其连接。
PrintWriter pw=new PrintWriter("pw.txt","utf-8");
pw.println("我祈祷拥有一颗透明的心灵");
- 在流连接中使用PrintWriter
FileOutputStream fos=new FileOutputStream("pw2.txt");
PrintWriter pw=new PrintWriter(
new OutputStreamWriter(
(new FileOutputStream("pw2.txt")),"utf-8")
);
pw.println("你好!");
java.io.BufferedReader
缓冲字符输入流
BufferedReader提供了读取一行字符串的方法:
String readLine()
该方法会顺序读取若干字符,直到读取了换行符为止,然后将换行符
之前的所有字符以一个字符串形式返回。
若返回值为null,则表示文件末尾
可以按行读取字符串
FileInputStream fis=new FileInputStream("."+File.separator+
"src"+File.separator)
InputStreamReader isr=new InputStreamReader(fis,"utf-8");
BufferedReader br=new BufferedReader(isr);
String line=null;
while((line=br.readLine())!=null){
System.out.println(line);
}
异常
##throw和throws
-
throw
用于抛出一个异常 -
通常两种情况会要求抛出一个异常
* 1:当方法中出现了一个满足语法要求,但是不满足业务逻辑要求时,可以做为一个异常抛给调用者,通知他这样的操作不允许。
* 2:当前方法代码中确实出现了异常,但是该异常不应该由当前方法来解决时可以将其抛给调用者。 -
throws
-
当一个方法中使用throw抛出一个异常时,除RuntimeException及其子类异常编译器要求必须在该方法上使用throws声明这类异常的抛出。否则编译不通过
-
当调用的一个方法含有throws声明异常抛出时,编译器会检查调用该方法的代码有没有处理该异常,没有则编译不同过。
- 处理手段有两种:
- 1:通过try-catch捕获该异常
- 2:在当前方法上继续使用throws声明抛出该异常 -
当父类中的方法含有throws抛出异常时
子类对该方法重写的准则
- 可以不抛出任何异常
- 可以抛出原异常
- 可以抛出部分异常
- 可以抛出父类方法抛出异常的子类型异常
- 不允许抛出额外异常
- 不允许抛出父类方法抛出异常的父类型异常
##finally块
- finally块只能定义在异常捕获机制的最后一块
- finally能确保无论try块中的代码是否抛出异常,finally块中的代码都会被执行,除非停止虚拟机:
system.exit(0):正常退出,程序正常执行结束退出
system.exit(1):是非正常退出,无论程序是否正在执行,都退出 - 所以finally块中的代码通常都是无关乎程序是否报错都要运行的代码。比如在IO中,关闭流的操作就应当放在finally块中确保执行。
##自定义异常
自定义异常通常用来说明业务逻辑级别的异常。
- 继承Exception
- 生成序列号:
private static final long serialVersionUID =-8241398737954702031L; - 点击source->generate constructors form superclass
##异常中常用方法
- e.printStackTrace();//输出执行堆栈信息
- e.getMessage()得到有关异常事件的信息
- Throwable getCause()获取该异常出现的原因,一直没用过。