JAVA-SE中:集合,IO流,文件与异常

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遍历方法

  1. 遍历所有的key
/*
 * 遍历所有的key:
 * Set<K> keySet()
 * 将当前Map中所有的key以一个Set集合形式返回
 * 所以遍历这个Set集合就等同于遍历了所有的key
 */
Set<String> keySet = map.keySet();
for(String key : keySet){
	System.out.println("key:"+key);
}
  1. 遍历每一组键值对(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);
}
  1. 遍历所有的value
/*
 * 遍历所有value
 * Collection<V> values()
 * 将当前Map中所有的value存入一个集合后返回
 */
Collection<Integer> values = map.values();
for(Integer value : values){
	System.out.println("value:"+value);			
}
  1. 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存储数据的过程

  1. 获取key的hashCode值
  2. 根据hashCode值,通过hash算法,计算键值对的存放位置。将键值对以Entry实例存入,存放方式为链表
  3. 若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有如下要求:

  1. 如果两个对象的equals的结果是相等的,那么两个对象的hashCode值返回的结果也必须是相同的
  2. 任何时候重写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可以

  1. 访问该文件或目录的属性信息(名字,大小,修改日期等)
  2. 操作文件或目录(创建,删除)
  3. 若表示的是目录,可以查看该目录中的子项信息,但是不能访问一个文件中的数据

构造方法

  • 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流

流的一种分类方式:

  1. 输出流(output) 用来写出数据
  2. 输入流(input) 用来读取数据

另一种分类方式:

  1. 低级流:是真实负责读写的流,直接连接数据源,负责将数据搬运。特点:数据源明确,知道数据从哪里来,或者数据写到哪里去
  2. 高级流:高级流基于低级流,提供的额外功能简化读写数据的操作。

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,则写入的数据都是追加在文件末尾
    • 打开文件,如果不存在,会自动创建一个文件。如果没有权限创建文件,则 抛出异常。如果文件已经存在,文件将被删除,重新换掉

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块中确保执行。

##自定义异常
自定义异常通常用来说明业务逻辑级别的异常。

  1. 继承Exception
  2. 生成序列号:
    private static final long serialVersionUID =-8241398737954702031L;
  3. 点击source->generate constructors form superclass

##异常中常用方法

  • e.printStackTrace();//输出执行堆栈信息
  • e.getMessage()得到有关异常事件的信息
  • Throwable getCause()获取该异常出现的原因,一直没用过。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值