自学javase的回顾(4/5)
回顾第四天,还有最后一篇了…
1、集合和Collection接口
集合:
什么是集合?
能够存储多种数据类型的容器。相比较数组来说,更加灵活。
集合分为两种:
1、Collection(以单个的方式进行存储)
Collection有两个常用的子接口:
(1)List,特点:有序可重复,元素有下标(注:这里的有序不代表从小到大,而是存储进去的顺序和取出来的顺序一致)
List接口中有3个常用的子类:
[1]ArrayList:是非线程安全的,底层采用了数组的数据结构
[2]LinkList:采用了双向链表的数据结构
[3]Vector:是线程安全的,底层采用了数组的数据结构
(2)Set,特点:无序不可重复,元素没有下标
Set接口中有2个常用的子类:
[1]HashSet:底层采用了哈希表,实际上底层new了一个HashMap
[2]TreeSet:底层采用了二叉树数据结构,实际上底层new了一个TreeMap,它的上面还有一个父接口:SortedSet。它有一个特点:会从小到大自动排序
2、Map(以键值对的方式进行存储)
Map下有3个常用的子接口:
(1)HashMap:非线程安全的,底层是哈希表
(2)HashTable:线程安全的,底层是哈希表
(3)TreeMap:底层是二叉树,它的上面还有一个父接口SortedMap
Collection能存储什么数据?
在没有使用泛型之前,Collection能存储Object下所有的子类。
使用了泛型之后,Collection只能存储泛型指定的类。
什么是泛型?
泛型的本质是参数化类型,就是说所操作的数据类型被指定为一个参数。简化说,就是指定数据类型。
Collection有哪些常用的方法?
1、add方法:向集合中添加元素
2、size方法:查看集合中元素的个数,或者说集合的大小
3、remove方法:移除集合中的某个元素
4、clear方法:清楚集合中的所有元素
5、contains方法:查看集合中是否存在这个元素
6、isEmpty方法:集合中是否为空
7、toArray方法:将集合转换为数组
8、Collection.sort(list):将集合排序
public class Test {
public static void main(String[] args) {
Collection<Integer> l=new ArrayList<>();
l.add(1);
l.add(2);
System.out.println(l.isEmpty());//false
System.out.println(l.contains(1));//true
l.remove(1);
l.clear();
System.out.println(l.size());//0
l.add(45);
Object[] i=l.toArray();
for(Object o:i){
System.out.println(o);//45
}
}
}
关于Collection中的遍历元素有两种方法:
1、构建迭代器(Iterator):
迭代器中的常用方法:
(1)hasnext()返回布尔类型,代表下次迭代是否还有元素;
(2)next指向下一个元素;
(3)remove移除迭代器指向的元素;
public class Test {
public static void main(String[] args) {
Collection<Integer> l=new ArrayList<>();
l.add(1);
l.add(2);
Iterator it=l.iterator();
while (it.hasNext()){
Object i=it.next();
System.out.println(i);//1 2
}
}
}
2、foreach
public class Test {
public static void main(String[] args) {
Collection<Integer> l=new ArrayList<>();
l.add(1);
l.add(2);
for(Integer i:l){
System.out.println(i);//1 2
}
}
}
2、List接口和Set接口
List接口常用方法:
特点:有序可重复
由于有了下标,方法和Collection有不同之处,也多出了新的遍历方式
1、add(下标,元素):添加元素到指定的下标
2、get(下标):获取指定下标的元素
3、indexOf(元素):获取元素的下标
public class Test {
public static void main(String[] args) {
List<Integer> l=new ArrayList<>();
l.add(0,1);
l.add(1,2);
Integer i=l.get(0);
System.out.println(i);//1
int i1=l.indexOf(2);
System.out.println(i1);//1
}
}
新的遍历方式:
public class Test {
public static void main(String[] args) {
List<Integer> l=new ArrayList<>();
l.add(0,1);
l.add(1,2);
for(int i=0;i<l.size();i++){
System.out.println(l.get(i));//1 2
}
}
}
List下常用的三大子接口:
1、ArrayList:
ArrayList的构造方法有一个参数,是指定初始化容量的,本身的初始化容量是10
如果扩容(add)的话,它的默认扩容倍数是1.5倍,在源代码中使用到了位运算符>>1。
番外:>>位运算符,二进制右移几位,<<就是左移
由于其扩容效率低的问题,所以一般指定好ArrayList的初始容量。
优点:查询快(因为索引的内存地址是连续的)
缺点:增删慢(因为添加元素就是,新建一个新的大的数组,然后复制过去,比较麻烦)
2、LinkedList:
LinkedList底层是一个双向链表没有初始化容量,单向链表组成是由节点组成,每个节点都有存储的数据和指向下一个元素的内存地址。双向链表也是如此,只不过双向链表的每个节点都有存储的数据和指向上一个元素的内存地址、指向下一个元素的内存地址。由于指向上一个或下一个节点的特点:
优点:增删快(因为添加元素,就是添加节点,只需要在节点上加两个引用)
缺点:查询慢(因为从头开始查询,经过一个又一个节点)
注:链表不推荐使用for循环遍历,推荐迭代器和foreach,foreach底层也是迭代器。
3、Vector:
底层也是一个数组,初始化容量也是10,默认扩容倍数是2倍。
是线程安全的,效率比较低,用的比较少。
如果想要将ArrayList集合转换成线程安全的,就用Collection.synchronizedList(集合名)。
Set接口:
Set集合特点:无序不可重复
Set接口有两个常用的子接口:
1、HashSet:底层是新建了一个HashMap,而HashSet做HashMap的key部分
2、TreeSet:底层是新建了一个TreeMap,而TreeSet做TreeMap的key部分,二叉树的数据结构是左小右大的数据结构,TreeSet属于中序遍历,它能够自动排序。
如果TreeSet存放的是自己定义的类,就要自定义比较方法,不写的话无法完成自动排序。
自定义比较方法有两种:
1、在自定义类上实现Comparable接口
2、定义一个比较类,这个类也实现了Comparator接口,将这个比较类放到TreeSet构造方法参数中
第一种方法适合固定的比较方法
第二种方法适合随时修改的比较器
public class Test {
public static void main(String[] args) {
Set<Student> m=new TreeSet<>();
m.add(new Student("zhangsan"));
m.add(new Student("lisi"));
m.add(new Student("wangwu"));
for(Object o:m){
System.out.println(o);//lisi wangwu zhangsan
}
}
}
class Student implements Comparable<Student>{
String name;
public Student(String name) {
this.name = name;
}
@Override
public int compareTo(Student student) {
return this.name.compareTo(student.name);
}
@Override
public String toString() {
return name;
}
}
3、Map接口
Map接口:
Map接口存储数据是按键值对的方式进行存储的。
键值对包括key和value两个部分。
Map接口常用的方法:
1、put(key,value):向集合中添加元素
2、clear():清除集合中的元素
3、containsKey():集合中是否有key值
4、containsValue():集合中是否有value值
5、get(key):获取集合中key键的value值
6、isEmpty:集合中元素是否为空
7、keySet():获取所有key,返回值是一个set集合
8、remove(key):移除某个key和其对应的value
9、size():集合中元素的个数
10、values():获取元素中value值,返回一个Collection
11、entrySet():将一个map转换成一个set集合,返回Set<Map.Entry<K,V>>
public class Test {
public static void main(String[] args) {
Map<Integer,String> m=new HashMap<>();
m.put(0,"ad");
m.put(1,"ab");
m.put(2,"ax");
System.out.println(m.containsKey(1));//true
System.out.println(m.containsValue("ac"));//false
System.out.println(m.isEmpty());//false
m.remove(0);
System.out.println(m.size());//2
System.out.println(m.get(0));//null
Collection c=m.values();
for(Object c1:c){
System.out.println(c1);//ab ax
}
Set s=m.entrySet();
for(Object c2:s){
System.out.println(c2);//1=ab 2=ax//这种遍历效率比较高
}
}
}
Map接口有三个常用的子接口:
1、HashMap:
初始化容量是16,默认扩容倍数是2倍,它的数据结构是哈希表,每一个哈希值都会创建一个链表,哈希表就是数组和链表的集合体,数组中每一个元素都是一个链表。这样的结构既继承了数组的优点,又继承了链表的优点。
HashMap储存数据的流程:
首先,先将k和v封装到节点对象中,然后底层调用k的hashCode方法计算出数组下标,下标位置如果没有任何元素,就把节点存储到这个位置。如果说下标有数据,拿着k和equals方法,去和其他k作比较,如果返回的都是false就在链表末尾添加节点。如果其中一个返回true,则覆盖新的value。
所以如果HashMap集合分中存放自己定义的类的对象的时候,就要在类中重写equals和hashCode方法。
public class Test {
public static void main(String[] args) {
Map<Student,String> m=new HashMap<>();
m.put(new Student("zhangsan"),"1");
m.put(new Student("lisi"),"2");
m.put(new Student("zhangsan"),"3");
System.out.println(m.get(new Student("zhangsan")));//3
}
}
class Student{
String name;
public Student(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
2、TreeMap:
TreeMap的底层是一个二叉树,能够自动对K部分进行从小到大排序。也需要定义一个比较器。
public class Test {
public static void main(String[] args) {
Map<Student,Integer> m=new TreeMap<>(new CompareToStudent());
m.put(new Student("zhangsan"),1);
m.put(new Student("lisi"),2);
m.put(new Student("wangwu"),3);
Set set=m.entrySet();
for(Object s:set){
System.out.println(s);//lisi=2 wangwu=3 zhangsan=1
}
}
}
class Student{
String name;
public Student(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
class CompareToStudent implements Comparator<Student>{
@Override
public int compare(Student student, Student t1) {
return student.name.compareTo(t1.name);
}
}
3、HashTable的子接口Properties:
只需要掌握两个方法:
存储方法:setProperty()
获取方法:getPropety()
public class Test {
public static void main(String[] args) {
Properties properties=new Properties();
properties.setProperty("ab","ac");
System.out.println(properties.getProperty("ab"));//ac
}
}
4、IO流
什么是IO流?
是从内存中读取和存放文件的流
IO流分为两类:
1、万能流:
按照字节读取,基本上任何文件都可以读取
2、字符流:
按照字符读取,只能读取普通的文本文档
IO流有四个常用的工具:
1、FileInputStream:字节输入
2、FileOutputStream:字节输出
3、FileReader:字符输入
4、FileWriter:字符输出
注:所有流用后都应该关闭close(),输出流记得刷新flush()。
FileInputStream中常用的方法:
1、read():从头开始读取字节,返回类型为int,如果没有字节了就返回-1。
2、read(byte[]):将字节转读到byte数组中的个数,返回值是int。
3、available()返回流当中剩余没有读到的字节数据
4、skip(long n)跳过几个字节不读
public class Test {
public static void main(String[] args) {
FileInputStream fis=null;
try {
fis=new FileInputStream("cs.txt");//txt文本中内容为abc
byte[] b=new byte[fis.available];
int s=fis.read(b);
String s1=new String(b,0,s);
System.out.println(s1);//abc
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileOutputStream中常用的方法:
1、write(byte[]),将byte数组写入文件
2、构造方法(文件名,是否追加)
public class Test {
public static void main(String[] args) {
FileOutputStream fos=null;
try {
fos=new FileOutputStream("cs.txt",true);
String s="我是中国人";
byte[] b=s.getBytes();
fos.write(b);
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这样,就可以写出一个文件copy的代码实现:
public class Test {
public static void main(String[] args) {
FileOutputStream o=null;
FileInputStream i=null;
try {
i=new FileInputStream("C:\\Users\\Fade\\Desktop\\src\\os.txt");
o=new FileOutputStream("C:\\Users\\Fade\\Desktop\\os.txt");
byte[] b=new byte[100*1024];
int readDate=0;
while ((readDate=i.read(b))!=-1){
o.write(b,0,readDate);
}
o.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(o!=null){
try {
o.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(i!=null){
try {
i.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
同理,FileReader和FileWriter传的是char[],而不是byte[]。
另外IO流还有:
1、转换流(将字节流转换成字符流)
InputStreamReader
OutputStreamWriter
2、缓冲流专属(不需要自定义数组,自带缓冲。它本身是包装流,包装节点流,节点流是包装流的构造参数,例如这个包装流的参数是Reader,Reader被叫做节点流)
BufferedReader
BufferedWriter
BufferedInputStream
BufferedOutputStream
3、数据流专属(这个流可以将数据和数据本身的类型传入或者读取出来)
DataInputStream
DataOutputStream
4、标准输出流(指定终端信息打印位置)
PrintWriter
PrintStream
5、对象专属流(反序列化流,涉及到反射机制,将类反序列化读入或读取文件)
ObjectInputStream
ObjectOutputStream