集合类 集合类是Java中非常重要的存在,使用频率极高。集合其实与我们数学中的集合是差不多的概念,集合表示一组对象,每一个对象我们都可以称其为元素。不同的集合有着不同的性质,比如一些集合允许重复的元素,而另一些则不允许,一些集合是有序的,而其他则是无序的。
数组和集合的区别
数组初始化后大小不可变 数组只能按索引顺序存取 数组的大小是固定的,集合的大小是可变的 数组可以存放基本数据类型,但集合只能存放对象 集合的根类 Java标准库自带的java.util包提供了集合类:Collection,它是除Map外所有其他集合类的根接口。Java的java.util包主要提供了以下三种类型的集合:
List:一种有序列表的集合,例如,按索引排列的Student的List Set:一种保证没有重复元素的集合,例如,所有无重复名称的Student的Set Map:一种通过键值(key-value)查找的映射表集合,例如,根据Student的name查找对应Student的Map List集合 在List接口中,定义了列表类型需要支持的全部操作,List直接继承自前面介绍的Collection接口,其中很多地方重新定义了一次Collection接口中定义的方法,这样做是为了更加明确方法的具体功能
List的行为和数组几乎完全相同:List内部按照放入元素的先后顺序存放,每个元素都可以通过索引确定自己的位置,List的索引和数组一样,从0开始。
数组和List类似,也是有序结构,如果我们使用数组,在添加和删除元素的时候,会非常不方便。例如,一个ArrayList拥有5个元素,实际数组大小为6(即有一个空位):
List集合特点
是一个有序的集合,插入元素默认是插入到尾部,按顺序从前往后存放,每个元素都有一个自己的下标位置 列表中允许存在重复元素 ArrayList 在ArrayList中,底层就是采用数组实现的,跟数据结构中的顺序表思路差不多,可以参考顺序表查找操作
集合类在删除元素时,只会调用equals方法进行判断是否为指定元素,而不是进行等号判断,所以说一定要注意,如果两个对象使用equals方法相等,那么集合中就是相同的两个对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.util.ArrayList;import java.util.List;public class Main { public static void main (String[] args) throws CloneNotSupportedException { List<Integer> list = new ArrayList<>(); list.add(1 ); list.add(2 ); list.add(3 ); list.add(4 ); list.add(5 ); list.add(6 ); list.add(new Integer(20 )); list.remove(new Integer(20 )); list.remove((Integer) 10 ); System.out.println(list); } }
生成只读List
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.util.ArrayList;import java.util.Arrays;import java.util.List;public class Main { public static void main (String[] args) throws CloneNotSupportedException { List<String> list = Arrays.asList("A" , "B" , "C" ); System.out.println(list); List<String> listStatic = new ArrayList<String>() {{ add("A" ); add("B" ); add("C" ); }}; System.out.println(listStatic); } }
LinkedList LinkedList同样是List的实现类,只不过它是采用的链式实现,也就是我们之前讲解的链表,只不过它是一个双向链表,也就是同时保存两个方向。LinkedList不仅可以当做List来使用,也可以当做双端队列使用
LinkedList和ArrayList区别 ArrayList LinkedList 获取指定元素 速度很快 需要从头开始查找元素 添加元素到末尾 速度很快 速度很快 在指定位置添加/删除 需要移动元素 不需要移动元素 内存占用 少 较大
List遍历 for循环遍历
1 2 3 4 5 6 7 8 9 10 import java.util.List;public class Main { public static void main (String[] args) throws CloneNotSupportedException { List<String> list = List.of("apple" , "pear" , "banana" ); for (int i=0 ; i<list.size(); i++) { String s = list.get(i); System.out.println(s); } } }
迭代器遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.Iterator;import java.util.List;public class Main { public static void main (String[] args) throws CloneNotSupportedException { List<String> list = List.of("apple" , "pear" , "banana" ); for (Iterator<String> it = list.iterator(); it.hasNext(); ) { String s = it.next(); System.out.println(s); } System.out.println("-------" ); for (String s : list) { System.out.println(s); } } }
Queue和Deque 队列Queue实现了一个先进先出(FIFO)的数据结构:
通过add()/offer()方法将元素添加到队尾; 通过remove()/poll()从队首获取元素并删除; 通过element()/peek()从队首获取元素但不删除。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.util.Deque;import java.util.LinkedList;import java.util.Queue;public class Main { public static void main (String[] args) throws CloneNotSupportedException { Queue<String> queueList = new LinkedList<>(); queueList.offer("AAA" ); queueList.offer("BBB" ); System.out.println(queueList.poll()); System.out.println(queueList.poll()); System.out.println("-----" ); Deque<String> dequeList = new LinkedList<>(); dequeList.push("AAA" ); dequeList.push("BBB" ); System.out.println(dequeList.pop()); System.out.println(dequeList.pop()); } }
Set Set用于存储不重复的元素集合,它主要提供以下几个方法:
将元素添加进Set:boolean add(E e) 将元素从Set删除:boolean remove(Object e) 判断是否包含元素:boolean contains(Object e) Set实际上相当于只存储key、不存储value的Map。我们经常用Set用于去除重复元素。因为放入Set的元素和Map的key类似,都要正确实现equals()和hashCode()方法,否则该元素无法正确地放入Set。最常用的Set实现类是HashSet,实际上,HashSet仅仅是对HashMap的一个简单封装
HashSet HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.util.HashSet;import java.util.Set;public class Main { public static void main (String[] args) { Set<String> set = new HashSet<>(); set.add("apple" ); set.add("banana" ); set.add("pear" ); set.add("orange" ); for (String s : set) { System.out.println(s); } } }
TreeSet TreeSet是有序的,因为它实现了SortedSet接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.util.Set;import java.util.TreeSet;public class Main { public static void main (String[] args) { Set<String> set = new TreeSet<>(); set.add("apple" ); set.add("banana" ); set.add("pear" ); set.add("orange" ); for (String s : set) { System.out.println(s); } } }
Map Map集合是为了实现键值映射而存在的。Map<K, V>是一种键-值映射表,当我们调用put(K key, V value)方法时,就把key和value做了映射并放入Map。当我们调用V get(K key)时,就可以通过key获取到对应的value。如果key不存在,则返回null。和List类似,Map也是一个接口,最常用的实现类是HashMap。Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉。
HashMap HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。HashMap 是无序的,即不会记录插入的顺序。
HashMap的key与value类型可以相同也可以不同,可以是字符串(String)类型的key和value,也可以是整型(Integer)的key和字符串(String)类型的value。
HashMap之所以能根据key直接拿到value,原因是它内部通过空间换时间的方法,用一个大数组存储所有value,并根据key直接计算出value应该存储在哪个索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import java.util.HashMap;import java.util.Map;public class Main { public static void main (String[] args) throws CloneNotSupportedException { Map<String, Integer> map = new HashMap<>(); map.put("apple" , 123 ); map.put("pear" , 456 ); map.put("banana" , 789 ); map.put("apple" , 321 ); map.putIfAbsent("pear" , 654 ); for (String key : map.keySet()) { Integer value = map.get(key); System.out.println(key + " = " + value); } System.out.println("-------------" ); for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + " = " + value); } } }
重新equals和hashCode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import java.util.HashMap;import java.util.Objects;public class Main { public static void main (String[] args) { HashMap<Student, String> hashMap = new HashMap<>(); Student a = new Student("张三" ,23 ); Student b = new Student("李四" ,21 ); hashMap.put(a, "hello" ); hashMap.put(b, "hello" ); String s1 = hashMap.get(a); String s2 = hashMap.get(b); System.out.println(s1); System.out.println(s2); } }class Student { private String name; private int score; public Student () { } public Student (String name, int score) { this .name = name; this .score = score; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getScore () { return score; } public void setScore (int score) { this .score = score; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Student student = (Student) o; return score == student.score && Objects.equals(name, student.name); } @Override public int hashCode () { return Objects.hash(name, score); } }
LinkedHashMap 对HashMap的插入进行了修改,保证读取到的顺序和插入的顺序是相同的。由哈希表保证键的唯一性,由链表保证键的有序(存储和取出的顺序一致)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.util.LinkedHashMap;import java.util.Set;public class Main { public static void main (String[] args) throws CloneNotSupportedException { LinkedHashMap<String, String> hm = new LinkedHashMap<String, String>(); hm.put("2345" , "hello" ); hm.put("1234" , "world" ); hm.put("3456" , "java" ); Set<String> set = hm.keySet(); for (String key : set) { String value = hm.get(key); System.out.println(key + "---" + value); } } }
TreeMap 就像它的名字一样,就是一个Tree,它的内部直接维护了一个红黑树(没有使用哈希表)因为它会将我们插入的结点按照规则进行排序,所以说直接采用红黑树会更好,我们在创建时,直接给予一个比较规则即可,跟之前的TreeSet是一样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import java.util.Comparator;import java.util.Map;import java.util.TreeMap;public class Main { public static void main (String[] args) { Map<Student, Integer> map = new TreeMap<>(new Comparator<Student>() { public int compare (Student p1, Student p2) { return p1.score > p2.score ? -1 : 1 ; } }); map.put(new Student("Tom" , 77 ), 1 ); map.put(new Student("Bob" , 66 ), 2 ); map.put(new Student("Lily" , 99 ), 3 ); for (Student key : map.keySet()) { System.out.println(key); } } }class Student { public String name; public int score; Student(String name, int score) { this .name = name; this .score = score; } public String toString () { return String.format("{%s: score=%d}" , name, score); } }
迭代器 Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代ArrayList和HashSe 等集合。Iterator是Java 迭代器最简单的实现,ListIterator 是 Collection API 中的接口,它扩展了Iterator 接口。
实现迭代 集合类实现Iterable接口,该接口要求返回一个Iterator对象; 用Iterator对象迭代集合内部数据。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class Main { public static void main (String[] args) { ReverseList<String> rlist = new ReverseList<>(); rlist.add("Apple" ); rlist.add("Orange" ); rlist.add("Pear" ); for (String s : rlist) { System.out.println(s); } } }class ReverseList <T > implements Iterable <T > { private List<T> list = new ArrayList<>(); public void add (T t) { list.add(t); } @Override public Iterator<T> iterator () { return new ReverseIterator(list.size()); } class ReverseIterator implements Iterator <T > { int index; ReverseIterator(int index) { this .index = index; } @Override public boolean hasNext () { return index > 0 ; } @Override public T next () { index--; return ReverseList.this .list.get(index); } } }
Stream流 Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作的处理,最后由最终操作(terminal operation)得到前面处理的结果。
生成方式 Stream流可以通过集合(最常用),数组,值,文件,函数等方式来进行生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 List<Integer> integerList = Arrays.asList(1 , 2 , 3 , 4 , 5 , 6 ); Stream<Integer> streamList = integerList.stream();int [] intArr = {1 , 2 , 3 , 4 , 5 , 6 }; IntStream streamArr = Arrays.stream(intArr); Stream<Integer> streamValue = Stream.of(1 , 2 , 3 , 4 , 5 , 6 ); Stream<String> lines = Files.lines(Paths.get("data.txt" ), Charset.defaultCharset()); Stream<Integer> streamIterator函数 = Stream.iterate(0 , n -> n + 2 ).limit(5 ); Stream<Double> streamGenerator = Stream.generate(Math::random).limit(5 );
中间操作 一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的,仅仅调用到这类方法,并没有真正开始流的遍历,真正的遍历需等到终端操作时,常见的中间操作有下面即将介绍的 filter、map 等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import java.io.IOException;import java.util.Arrays;import java.util.List;import java.util.stream.Collectors;import java.util.stream.Stream;public class Main { public static void main (String[] args) throws IOException { List<Integer> integerList = Arrays.asList(0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ); Stream<Integer> streamDistinct = integerList.stream().distinct(); integerList = streamDistinct.collect(Collectors.toList()); System.out.println(integerList); Stream<Integer> streamFilter = integerList.stream().filter(i -> i > 3 ); integerList = streamFilter.collect(Collectors.toList()); System.out.println(integerList); Stream<Integer> streamLimit = integerList.stream().limit(3 ); integerList = streamLimit.collect(Collectors.toList()); System.out.println(integerList); List<String> stringList = Arrays.asList("Java 8" , "Lambdas" , "In" , "Action" ); List<Integer> collect = stringList.stream() .map(String::length) .collect(Collectors.toList()); System.out.println(collect); List<String> wordList = Arrays.asList("Java 8" , "Lambdas" , "In" , "Action" ); List<String> strList = wordList.stream() .map(w -> w.split(" " )) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList()); System.out.println(strList); if (integerList.stream().allMatch(i -> i > 3 )) { System.out.println("所有元素值都大于3" ); } else { System.out.println("并非所有元素值都大于3" ); } } }
终端操作 一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。终端操作的执行,才会真正开始流的遍历。如下面即将介绍的 count、collect 等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 import java.io.IOException;import java.util.*;import java.util.stream.Collectors;import static java.util.stream.Collectors.*;public class Main { public static void main (String[] args) throws IOException { List<Integer> integerList = Arrays.asList(0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ); Long result = integerList.stream().count(); System.out.println(result); System.out.println("---------------------------------------" ); Optional<Integer> resultFind = integerList.stream().filter(i -> i > 3 ).findFirst(); System.out.println(resultFind.orElse(-1 )); System.out.println("---------------------------------------" ); List<String> stringList = Arrays.asList("Java 8" , "Lambdas" , "In" , "Action" ); int sum = integerList.stream() .reduce(0 , Integer::sum); Optional<Integer> min = stringList.stream() .map(String::length) .reduce(Integer::min); Optional<Integer> max = stringList.stream() .map(String::length) .reduce(Integer::max); System.out.println("max=" + max + ",min=" + min + ",sum=" + sum); System.out.println("---------------------------------------" ); Optional<Integer> minStr = stringList.stream() .map(String::length) .min(Integer::compareTo); Optional<Integer> maxStr = stringList.stream() .map(String::length) .max(Integer::compareTo); OptionalInt minOp = stringList.stream() .mapToInt(String::length) .min(); OptionalInt maxOp = stringList.stream() .mapToInt(String::length) .max(); Optional<Integer> minRe = stringList.stream() .map(String::length) .reduce(Integer::min); Optional<Integer> maxRe = stringList.stream() .map(String::length) .reduce(Integer::max); System.out.println("minStr=" + minStr + ",maxStr=" + maxStr); System.out.println("minOp=" + minOp + ",maxOp=" + maxOp); System.out.println("minRe=" + minRe + ",maxRe=" + maxRe); System.out.println("---------------------------------------" ); IntSummaryStatistics intSummaryStatistics = stringList.stream() .collect(summarizingInt(String::length)); System.out.println(intSummaryStatistics.getAverage() + "," + intSummaryStatistics.getSum()); System.out.println("---------------------------------------" ); stringList.stream().forEach(System.out::println); System.out.println("---------------------------------------" ); List<Integer> intList = stringList.stream() .map(String::length) .collect(toList()); System.out.println(intList); Set<Integer> intSet = stringList.stream() .map(String::length) .collect(toSet()); System.out.println(intSet); System.out.println("---------------------------------------" ); String str = stringList.stream() .map(String::toLowerCase) .collect(Collectors.joining("-" )); System.out.println(str); } }
IO操作 JDK提供了一套用于IO操作的框架,为了方便我们开发者使用,就定义了一个像水流一样,根据流的传输方向和读取单位,分为字节流InputStream和OutputStream以及字符流Reader和Writer的IO框架,当然,这里的Stream并不是前面集合框架认识的Stream,这里的流指的是数据流,通过流,我们就可以一直从流中读取数据,直到读取到尽头,或是不断向其中写入数据,直到我们写入完成,而这类IO就是我们所说的BIO,
字节流一次读取一个字节,也就是一个byte的大小,而字符流顾名思义,就是一次读取一个字符,也就是一个char的大小(在读取纯文本文件的时候更加适合)
File对象 Java的标准库java.io提供了File对象来操作文件和目录
创建File对象本身不涉及IO操作; 可以获取路径/绝对路径/规范路径:getPath()/getAbsolutePath()/getCanonicalPath(); 可以获取目录的文件和子目录:list()/listFiles(); 可以创建或删除文件和目录。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Main { public static void main (String[] args) throws IOException { File f = new File("C:\\Windows" ); File[] fs1 = f.listFiles(); printFiles(fs1); File[] fs2 = f.listFiles(new FilenameFilter() { public boolean accept (File dir, String name) { return name.endsWith(".exe" ); } }); printFiles(fs2); } static void printFiles (File[] files) { System.out.println("==========" ); if (files != null ) { for (File f : files) { System.out.println(f); } } System.out.println("==========" ); } }
其他方法 Java还提供了Path和Files等方法方便来进行文件和目录的管理
InputStream就是Java标准库提供的最基本的输入流。它位于java.io这个包里。java.io包提供了所有同步IO的功能。要特别注意的一点是,InputStream并不是一个接口,而是一个抽象类,它是所有输入流的超类。
Java标准库的java.io.InputStream定义了所有输入流的超类 FileInputStream实现了文件流输入 ByteArrayInputStream在内存中模拟一个字节流输入FileInputStream是InputStream的一个子类。顾名思义,FileInputStream就是从文件流中读取数据。读取或写入IO流的过程中,可能会发生错误,例如,文件不存在导致无法读取,没有写权限导致写入失败,等等,这些底层错误由Java虚拟机自动封装成IOException异常并抛出。因此,所有与IO操作相关的代码都必须正确处理IOException 利用Java 7引入的新的try(resource)的语法,只需要编写try语句,让编译器自动为我们关闭资源。推荐的写法如下:
1 2 3 4 5 6 7 8 public void readFile () throws IOException { try (InputStream input = new FileInputStream("src/readme.txt" )) { int n; while ((n = input.read()) != -1 ) { System.out.println(n); } } }
InputStream提供了两个重载方法来支持读取多个字节 int read(byte[] b):读取若干字节并填充到byte[]数组,返回读取的字节数 int read(byte[] b, int off, int len):指定byte[]数组的偏移量和最大填充数
1 2 3 4 5 6 7 8 9 10 public void readFile () throws IOException { try (InputStream input = new FileInputStream("src/readme.txt" )) { byte [] buffer = new byte [1000 ]; int n; while ((n = input.read(buffer)) != -1 ) { System.out.println("read " + n + " bytes." ); } } }
ByteArrayInputStream可以在内存中模拟一个InputStream。ByteArrayInputStream实际上是把一个byte[]数组在内存中变成一个InputStream
1 2 3 4 5 6 7 8 9 10 11 public class Main { public static void main (String[] args) throws IOException { byte [] data = { 72 , 101 , 108 , 108 , 111 , 33 }; try (InputStream input = new ByteArrayInputStream(data)) { int n; while ((n = input.read()) != -1 ) { System.out.println((char )n); } } } }
从文件中读取所有字节,并转换成char然后拼成一个字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import java.io.ByteArrayInputStream;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;public class Main { public static void main (String[] args) throws IOException { String strFile; try (InputStream input = new FileInputStream("src/Main.java" )) { strFile = readAsString(input); } System.out.println(strFile); System.out.println("-----------------------------------------------" ); byte [] data = { 72 , 101 , 108 , 108 , 111 , 33 }; try (InputStream input = new ByteArrayInputStream(data)) { String str = readAsString(input); System.out.println(str); } } public static String readAsString (InputStream input) throws IOException { int n; StringBuilder sb = new StringBuilder(); while ((n = input.read()) != -1 ) { sb.append((char ) n); } return sb.toString(); } }
OutputStream 和InputStream类似,OutputStream也是抽象类,它是所有输出流的超类。
ByteArrayOutputStream在内存中模拟一个字节流输出 某些情况下需要手动调用OutputStream的flush()方法来强制输出缓冲区 总是使用try(resource)来保证OutputStream正确关闭 FileOutputStream 和InputStream类似,OutputStream也提供了close()方法关闭输出流,以便释放系统资源。要特别注意:OutputStream还提供了一个flush()方法,它的目的是将缓冲区的内容真正输出到目的地。
1 2 3 4 5 public void writeFile () throws IOException { try (OutputStream output = new FileOutputStream("out/readme.txt" )) { output.write("Hello" .getBytes("UTF-8" )); } }
ByteArrayOutputStream ByteArrayOutputStream可以在内存中模拟一个OutputStream。ByteArrayOutputStream实际上是把一个byte[]数组在内存中变成一个OutputStream
1 2 3 4 5 public void writeFile () throws IOException { try (OutputStream output = new FileOutputStream("out/readme.txt" )) { output.write("Hello" .getBytes("UTF-8" )); } }
同时操作多个AutoCloseable资源时,在try(resource) { … }语句中可以同时写出多个资源,用;隔开
1 2 3 4 5 try (InputStream input = new FileInputStream("input.txt" ); OutputStream output = new FileOutputStream("output.txt" )) { input.transferTo(output); }
Filter模式 通过一个“基础”组件再叠加各种“附加”功能组件的模式,称之为Filter模式(或者装饰器模式:Decorator)。它可以让我们通过少量的类来实现各种功能的组合
Java的IO标准库使用Filter模式为InputStream和OutputStream增加功能:
可以把一个InputStream和任意个FilterInputStream组合 可以把一个OutputStream和任意个FilterOutputStream组合 Filter模式可以在运行期动态增加功能(又称Decorator模式)
输入字节进行计数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class Main { public static void main (String[] args) throws IOException { byte [] data = "hello, world!" .getBytes("UTF-8" ); try (CountInputStream input = new CountInputStream(new ByteArrayInputStream(data))) { int n; while ((n = input.read()) != -1 ) { System.out.println((char )n); } System.out.println("Total read " + input.getBytesRead() + " bytes" ); } } }class CountInputStream extends FilterInputStream { private int count = 0 ; CountInputStream(InputStream in) { super (in); } public int getBytesRead () { return this .count; } public int read () throws IOException { int n = in.read(); if (n != -1 ) { this .count ++; } return n; } public int read (byte [] b, int off, int len) throws IOException { int n = in.read(b, off, len); if (n != -1 ) { this .count += n; } return n; } }
序列号和反序列化 序列化: 对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。
反序列化: 客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import java.io.*;public class Main { public static void main (String[] args) throws IOException, ClassNotFoundException { Person person = new Person("张三" , 24 ); OutputStream out = new FileOutputStream("readme.txt" ); try (ObjectOutputStream output = new ObjectOutputStream(out)) { output.writeObject(person); } catch (IOException e) { e.printStackTrace(); } FileInputStream in = new FileInputStream("readme.txt" ); try ( ObjectInputStream input = new ObjectInputStream(in)){ Person p = (Person) input.readObject(); System.out.println(p); } } }class Person implements Serializable { private String name; private int age; public Person () { System.out.println("无参构造." ); } public Person (String name, int age) { this .name = name; this .age = age; System.out.println("有参构造." ); } @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
Reader Reader是Java的IO库提供的另一个输入流接口。和InputStream的区别是,InputStream是一个字节流,即以byte为单位读取,而Reader是一个字符流,即以char为单位读取。java.io.Reader是所有字符输入流的超类
InputStream Reader 字节流,以byte
为单位 字符流,以char
为单位 读取字节(-1,0~255):int read()
读取字符(-1,0~65535):int read()
读到字节数组:int read(byte[] b)
读到字符数组:int read(char[] c)
FileReader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.io.*;import java.nio.charset.StandardCharsets;public class Main { public static void main (String[] args) throws IOException { try (Reader reader = new FileReader("src/Main.java" , StandardCharsets.UTF_8)) { int content; while ((content = reader.read()) != -1 ) { System.out.print((char ) content); } } } }
BufferedReader 1 2 3 4 5 6 7 8 9 10 11 import java.io.*;import java.nio.charset.StandardCharsets;public class Main { public static void main (String[] args) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("src/Main.java" , StandardCharsets.UTF_8))) { br.lines().forEach(System.out::println); } } }
Writer Reader是带编码转换器的InputStream,它把byte转换为char,而Writer就是带编码转换器的OutputStream,它把char转换为byte并输出
OutputStream Writer 字节流,以byte
为单位 字符流,以char
为单位 写入字节(0~255):void write(int b)
写入字符(0~65535):void write(int c)
写入字节数组:void write(byte[] b)
写入字符数组:void write(char[] c)
无对应方法 写入String:void write(String s)
FileWriter FileWriter就是向文件中写入字符流的Writer。它的使用方法和FileReader类似
1 2 3 4 5 6 7 8 9 10 11 12 import java.io.*;import java.nio.charset.StandardCharsets;public class Main { public static void main (String[] args) throws IOException { try (Writer writer = new FileWriter("readme.txt" , StandardCharsets.UTF_8)) { writer.write('H' ); writer.write("Hello" .toCharArray()); writer.write("Hello" ); } } }
OutputStreamWriter 除了CharArrayWriter和StringWriter外,普通的Writer实际上是基于OutputStream构造的,它接收char,然后在内部自动转换成一个或多个byte,并写入OutputStream。因此,OutputStreamWriter就是一个将任意的OutputStream转换为Writer的转换器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.io.*;import java.nio.charset.StandardCharsets;public class Main { public static void main (String[] args) throws IOException { String text = "sample text" ; try (FileOutputStream fos = new FileOutputStream("readme.txt" ); OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); BufferedWriter bf = new BufferedWriter(osw)) { bf.write(text); System.out.println("Successfully written data to the file" ); } } }
打印流 打印流其实我们从一开始就在使用了,比如System.out就是一个PrintStream,PrintStream也继承自FilterOutputStream类因此依然是装饰我们传入的输出流,但是它存在自动刷新机制,例如当向PrintStream流中写入一个字节数组后自动调用flush()方法。PrintStream也永远不会抛出异常,而是使用内部检查机制checkError()方法进行错误检查。最方便的是,它能够格式化任意的类型,将它们以字符串的形式写入到输出流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.io.*;import java.util.Scanner;public class Main { public static void main (String[] args) throws IOException { try (PrintStream stream = new PrintStream(new FileOutputStream("input.txt" ))){ stream.println("Hello,Java" ); }catch (IOException e){ e.printStackTrace(); } Scanner scanner = new Scanner(new File("input.txt" )); while (scanner.hasNextLine()) { System.out.println(scanner.nextLine()); } scanner.close(); } }