序列化 ,集合类, Object

一,处理流之:对象流

1.ObjectInputStream和ObjectOutputStream

用于存储和读取基木数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

  • 序列化: 用ObjectOutputStream类保存基本类型数据或对象的机制
  • 反序列化: 用ObjectInputStream类读取基木类型数据或对象的机制
  • objectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量 否则序列化的时候这些属性不会被序列化到二进制流中,反序列化后读取的该项数据为null; static是类的不是对象的所以不能序列化。

    2.对象的序列化

    1.对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传
    输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
    2.序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
    3.序列化是RMI(Remote Method Invoke-远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础
    4.如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常
    Serializable
    Externalizable
    /
    反序列化 将磁盘/网络文件中的对象还原为内存中的一个java对象
    /
    public static void oppositeSerializable() throws IOException, ClassNotFoundException {
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“object.bat”));
    Object obj=ois.readObject();
    String str= (String)obj;
    System.out.println(str);
    ois.close();
    }

public static void serializable() throws IOException {
/
序列化过程:将内存中的java对象爆粗难道磁盘中或通过网络传输出去
使用ObjectOutputStream实现
/
//1. 流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“object.bat”));
//2.显示过程
oos.writeObject(new String(“太酸了”));
oos.flush(); //刷新操作
oos.close();
}

3.自定义序列化对象

1.对象实现Serializable接口
2.定义全局变量seriaVersionUID 唯一标识符
3.除了当前person类需要实现Serializable接口外,还得保证其内部属性可以序列化,(默认基本数据类型都可以序列化)
4.序列化机制:
对象序列化机制允许把内存中的java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。 当其他程序获取了这种二进制流,就可以恢复成原来的java对象。
public class Person implements Serializable {

  1. public static final long serialVersionUID = 486541816186L;
  2. private String name;<br /> private int age;
  3. public Person(String name, int age) {<br /> this.name = name;<br /> this.age = age;<br /> }<br />}<br />ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.bat"));<br />oos.writeObject(new Person("名字",12));<br />oos.flush();<br />oos.close();<br />ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.bat"));<br />Person person = (Person) ois.readObject();<br />System.out.println(person);<br />ois.close();

3.1序列化版本标识符

1.凡是实现Serializable接口的类都有一个表示序列化版木标识符的静态变量:
1.private static final long serialVersionUID;
2.serialVersionUID用来表明类的不同版本间的兼容性l简言之,其目的是以 序列化对象进行版本控制,有关各版本反序列化时是否兼容。
3.如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内 部细节自动生成的。若类的实例变量做了修改,seriaVersionUID可能发生变 化。故建议,显式声明。
2.简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中 serialVersionUID与木地相应实体类的serialVersionUID进行比较,如果相同就 认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。 (InvalidCastException)
InvalidClassException: com.Person; local class incompatible: stream classdesc serialVersionUID = -7442840674689556895, local class serialVersionUID = -8175050223371111896

二,比较器

1.比较器两个接口Comparable和Comparator

1.1Comparable 自然排序

需要比较的类实现Comparable接口,然后重写compareTo方法。
1.像String、包装类等实现了Comparable接口重写了方法。
2.重写规则:如果this大于形参对象obj 则返回正整数,等于返回0,小于返回负整数。
3.像String和包装类重写compareTo()方法后从小到大排序。
public class Person implements Comparable {

  1. @Override<br /> public int compareTo(Person o) {<br /> return 0;<br /> }<br />}

1.2Comparator 定制排序

  • 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来排序,强行对多个对象进行整体排序的比较。
  • 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
  • 可以将Comparator传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。
  • 还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection提供排序。

public class MyComparator implements Comparator {

  1. @Override<br /> public int compare(Person o1, Person o2) {<br /> return 0;<br /> }<br />}

3.ComparabLe接口与Comparator的使用的对比:

1.Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小
2.Comparator接口属于临时性的比较。

三,集合

序列化 集合类 Object - 图1
image-20201016140757584
序列化 集合类 Object - 图2
image-20201016141140612
序列化 集合类 Object - 图3
image-20201016141345107
序列化 集合类 Object - 图4
image-20201016141414164
序列化 集合类 Object - 图5
image-20201016145008134

1.collection接口

== 比较的是地址,equals比较的是内容,Object的底层代码实现equals为== ,而String重写了equals方法,比较的两个对象的内容。所以如果对象没有重写equals方法则比较的就是地址。
所以要求:向Collection接口实现类的对象中添加数据obj时,要求obj所在类要重写equals()方法。否则contais()返回false。

1.contains(Object obj) contains(Collection c)

clear(); addAll();
重写Person方法
public class Person {
private int id;
private String name;

  1. public Person(int id, String name) {<br /> this.id = id;<br /> this.name = name;<br /> }
  2. @Override<br /> public boolean equals(Object o) {<br /> if (this == o) return true;<br /> if (o == null || getClass() != o.getClass()) return false;<br /> Person person = (Person) o;<br /> return id == person.id &&<br /> Objects.equals(name, person.name);<br /> }

/ @Override
public int hashCode() {
return Objects.hash(id, name);
}
/
}
@Test
public void test1(){
//contains
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);

  1. String s3 = "Tong";<br /> Person p = new Person(5,"jim");
  2. coll.add(s3);<br /> coll.add(456);<br /> coll.add(789);<br /> coll.add(p);<br /> // 1.判断单个元素是否存在集合中contains(Object obj)<br /> System.out.println(coll.contains(456));<br /> System.out.println(coll.contains("Tong"));<br /> System.out.println(coll.contains(new String("Tong")));<br /> System.out.println(coll.contains(s3));<br /> //未重写 equals方法 所以比较的地址<br /> System.out.println(coll.contains(new Person(5,"jim")));
  3. //2.containsAll(Collcetion c)<br /> Collection coll1 = new ArrayList();<br /> coll1.add(s3);<br /> coll1.add(456);<br /> System.out.println(coll.containsAll(coll1));<br /> coll1.add(4566);<br /> System.out.println(coll.containsAll(coll1));<br /> }

2.remove(Object obj),removerAll(Collection c)

同样需要重写equals方法因为删除元素时同样会用到equals方法。
removeAll 删除原集合中所有与c中有相同的元素
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);

  1. String s3 = "Tong";<br /> Person p = new Person(5,"jim");
  2. coll.add(s3);<br /> coll.add(456);<br /> coll.add(789);<br /> coll.add(p);
  3. //remove<br /> coll.remove(123);<br /> coll.remove( new Person(5,"jim"));<br /> System.out.println(coll);<br /> //removeAll()<br /> Collection coll1 = new ArrayList();<br /> coll1.add(456);<br /> coll1.add(789);
  4. coll.removeAll(coll1);<br /> System.out.println(coll);<br />}

3.retainAll(),equals()(顺序和元素必须都一致,因为逐个比较)

保留两个集合的交集
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);

  1. String s3 = "Tong";<br /> Person p = new Person(5,"jim");
  2. coll.add(s3);<br /> coll.add(456);<br /> coll.add(789);<br /> coll.add(p);
  3. System.out.println(coll);<br /> Collection coll1 = new ArrayList();<br /> coll1.add(456);<br /> coll1.add(789);<br /> coll1.add(852);
  4. coll.retainAll(coll1);<br /> System.out.println(coll);

}

4.hashCode() 获取哈希值

System.out.println(coll.hashCode());

5.toArray(); 集合转化为数组

Object[] arr= coll.toArray();
for(Object obj :arr) {
System.out.println(obj);
}

6.从数组到集合 Arrays.asList();

List list = Arrays.asList(“a”,”b”,”c”);
System.out.println(list);
// 不能写基本数据类型 (应该)
List list = Arrays.asList(1,2,3);
System.out.println(list);

7.iterator 迭代器

hasNext(); next(); 先指针下移,然后将下移之前的元素返回
iterator.remove();
//调用next方法 cursor下移,然后返回此时的元素。
//但是lastRet记录此时的元素位置,当调用remove方法时,移除的是当前位置的
注意:

  • lterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
  • 如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove都会报IllegalStateException。
  • 因为调用remove方法首先判断lastRet值是否大于0,初始状态为-1,调用一次remove之后lastRet变为-1.都会抛出

public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

  1. try {<br /> ArrayList.this.remove(lastRet);<br /> cursor = lastRet;<br /> lastRet = -1;<br /> expectedModCount = modCount;<br /> } catch (IndexOutOfBoundsException ex) {<br /> throw new ConcurrentModificationException();<br /> }<br />}

2.List集合:

方法
@Test
public void demo2(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add(“aa”);
list.add(new Person(5,”Tom”));
list.add(123);
list.add(465);
System.out.println(list);
//1.void add(int index,Object ele) 将元素插入到指定位置
list.add(1,”BB”);
System.out.println(list);
//2.addAll(Collection c)
list.addAll(Arrays.asList(1,2,5));
System.out.println(list.size());
System.out.println(list);
//3.Object get(int index) 获取指定位置的元素
System.out.println(list.get(2));
//4.int indexOf(Object obj) 如果不存在返回-1
//5.int lastIndexOf(Object obj)
System.out.println(list.indexOf(123));
System.out.println(list.lastIndexOf(123));
//6.remove(int index) 按照索引删除
System.out.println(list.remove(1));
System.out.println(list);
//7.Object set(int index,Object ele) 设置指定位置元素的值(替换)
list.set(0,”abc”);
System.out.println(list);
//8.subList(int fromIndex,int endIndex): 返回从fromIndex到toIndex 的子集合
System.out.println(list.subList(2,6));

}

List 接口和Collection接口中remove方法的重载

@Test
public void demo1(){
// Vector
ArrayList list = new ArrayList();
// LinkedList linked = new LinkedList();
list.add(0);
list.add(3);
list.add(1);
list.add(2);
list.remove(new Integer(2));
list.remove(2);
System.out.println(list);

  1. }

3.Set集合:

序列化 集合类 Object - 图6
image-20201017194739773

注:

  • 无序不可重复数据 但是这里的无序并不代表随机。
  • 无序指的是当放入数据的时候数据放入容器中的时候不是按顺序的而是随机分布。
  • 不可重复性:保证添加的元素按照equals判断时不能返回true;
  • Object的hashCode方法 可以理解为随机算出一个hash值,这样的话,任意两对象,大概率不会相同
  • Set接口中没有额外定义新的方法,使用的都是collection中声明过的方法。
  • 要求:向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
  • 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码

    1.添加元素的过程,以HashSet为例:

    我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否己经有元素:
    如果此位置上没有其他元素,则元素α添加成功。—->情况1
    如果此位置上有其他元絜b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
    如果hash值不相同,则元素α添加成功。—->情况2
    如果hash值相同,进而需要调用元素a所在类的equLas()方法:
    equals()返回true,元素α添加失败
    equals()返回false,则元素a添加成功。—->情况2
    对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
    jdk 7 :元素a放到数组中,指向原来的元素。头插法
    jdk 8∶原来的元素在数组中,指向元素a。尾插法
    总结:七上八下
    序列化 集合类 Object - 图7
    image-20201017191159628

    问题:为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?
  • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)

  • 并且31只占用5bits,相乘造成数据溢出的概率较小。
  • 31可以由i*31==(i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)
  • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)

    2.LinkedHashSet集合

    注:LinkedHashSet的使用
  • LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据的位置。(双向链表)。

  • 优点:对与频繁的遍历操作,LinkedHashSet的效率要高于HashSet

public class SetDemo {
@Test
public void demo(){
Set set = new HashSet();
set.add(465);
set.add(123);
set.add(123);
set.add(“AA”);
set.add(“CC”);
set.add(new User(“Tome”,5));
set.add(new User(“Tome”,5));
set.add(128);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void demo2(){
Set set = new LinkedHashSet();
set.add(465);
set.add(123);
set.add(123);
set.add(“AA”);
set.add(“CC”);
set.add(new User(“Tome”,5));
set.add(new User(“Tome”,5));
set.add(128);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}

3.TreeSet集合

注:
  • 可以按照添加对象的指定属性,进行排序。
  • 按照指定对象属性排序,所以只能放入相同类型的对象。 在创建对象是,写好泛型。
  • 两种排序方式,自然排序comparable和定制排序comparator.(所有之前用equals比较的方向,TreeSet都不用)
  • 自然排序中,比较两个对象是否相同的标准为: compareTo()返回e.不再是equals().
  • 定制排序中,比较两个对象是否相同的标准为: compare()返回e.不再是equals().
  • 两个排序方法中,相同对象后来者舍去。优先用compaer接口的排序规则。
  • TreeSet和之后的TreeMap采用红黑树的存储结构
  • 特点:有序,查询速度比List快

public class TreeSetDemo {
@Test
public void demo(){
TreeSet set = new TreeSet();
/set.add(132);
set.add(465);
set.add(789);
set.add(852);
set.add(52);
/
// set.add(“stirn”);
set.add(new User(“tom”,62));
set.add(new User(“tom”,5));
set.add(new User(“jexy”,35));
set.add(new User(“proxy”,1));
set.add(new User(“judi”,79));

  1. Iterator iterator = set.iterator();<br /> while (iterator.hasNext()){<br /> System.out.println(iterator.next());<br /> }
  2. }<br />}

4.例题

序列化 集合类 Object - 图8
image-20201018110304658

5.重要例题

@Test
public void demo3(){
HashSet set = new HashSet();
Person p1 = new Person(1001,”AA”);
Person p2 = new Person(1002,”BB”);

  1. set.add(p1);<br /> set.add(p2);<br /> p1.setName("CC");<br /> set.remove(p1);<br /> System.out.println(set);<br /> set.add(new Person(1001,"CC"));<br /> System.out.println(set);<br /> set.add(new Person(1001,"AA"));<br /> System.out.println(set);

}

4.Map

序列化 集合类 Object - 图9
image-20201018152823062
序列化 集合类 Object - 图10
image-20201019182500443
HashMap的底层结构:数组+链表 (jdk7之前)
数组+链表+红黑树(jdk 8 )
序列化 集合类 Object - 图11
image-20201018204512178

面试题:

1.HashMap的底层实现原理?
2.Hashtable和HashMap的区别
3.CurrentHashMap 线程安全

1.Map结构理解

序列化 集合类 Object - 图12
image-20201018205236498
Map中的key:无序的,不可重复的,使用Set存储所有的key ——>key所在的类要重写equals和hashCode方法
Map中的value:无序的,可重复的,使用collection存储所有的value ——->需要重写equals方法。
一个键值对:key-value构成了一个Entry对象
Map中的Entry:无序的,不可重复的,使用Set存储所有的entry

2.HashMap底层原理

JDK7.0 版本

HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
…可能已经执行过多次put…
map.put( key1, value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。——情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据
的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功 ——情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)
如果equals()返回false:此时key1-value1添加成功。——情况3
如果equals()返回true:使用value1替换value2.
补充:情况2和情况3,此时key1-value1 key2-value2采用链表的形式。
在不断的添加过程中,会涉及到扩容问题,超出零界值时,如果插入位置未空则不扩容,不为空就扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。

jdk8相较于jdk7在底层实现方面的不同:
  1. new HashMap():底层没有创建一个长度为16的数组
  2. jdk8底层的数组是:Node[],而非Entry[]
  3. 首次调用put()方法时,底层创建长度为16的数组
  4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
    当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。否则扩容。

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st TREEIFY_THRESHOLD=8 遍历长度大于等于8
treeifyBin(tab, hash);
break;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) // MIN_TREEIFY_CAPACITY =64
resize();

参数:
  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
  • DEFAULT_LOAD_FACTOR: HashMap的默认加载因子:0.75
  • threshold:扩容的临界值,=容量填充因子:16 0.75 =>12
  • TREEIFY_ THRESHOLD: Bucket中链表长度大于该默认值,转化为红黑树:8
  • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

    3.LinkedHashMap底层实现

    static class Entry extends HashMap.Node {
    Entry before, after;
    Entry(int hash, K key, V value, Node next) {
    super(hash, key, value, next);
    }
    }
    static class Node implements Map.Entry {
    final int hash;
    final K key;
    V value;
    Node next;

    Node(int hash, K key, V value, Node next) {
    this.hash = hash;
    this.key = key;
    this.value = value;
    this.next = next;
    }
    ……
    }

    5.Map常用的方法

    序列化 集合类 Object - 图13
    image-20201019183604049

    6.Hashtable -> properties

    //测试类无效
    Properties properties = new Properties();
    properties.load(new FileInputStream(“Serializable\jdbc.properties”));
    // properties.load(new FileInputStream(“jd.properties”));

    1. String name = properties.getProperty("name");<br /> String password =properties.getProperty("password");<br /> System.out.println(name);<br /> System.out.println(password);

    7.Collections 操作collection和map的工具类

    排序操作:(均为static方法)

  • reverse(List):反转List中元素的顺序

  • shuffle(List):对List集合元素进行随机排序
  • sort(List):根据元素的自然顺序对指定List集合元素按升序排序
  • sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
  • swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换

    查找替换

  • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素

  • Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
  • Object min(Collection)
  • Object min(Collection,Comparator)
  • int frequency(Collection,Object):返回指定集合中指定元素的出现次数
  • void copy(List dest,List src):将src中的内容复制到dest中
  • boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值

public class CollectionsTest {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(“412”);
list.add(“tome”);
list.add(“djaso”);
list.add(“andie”);
list.add(“4651”);
list.add(“412”);
// Collections.reverse(list);
// Collections.shuffle(list);
// Collections.sort(list);
Collections.swap(list,1,2);
System.out.println(list);

  1. int frequency = Collections.frequency(list,"412");<br /> System.out.println(frequency);<br /> //copy 尤其注意
  2. List list1 = Arrays.asList(new Object[list.size()]);<br /> /*List list1 = new ArrayList();<br /> list1.add(46);*/<br /> Collections.copy(list1,list);<br /> System.out.println(list1);<br /> System.out.println(list);<br /> // replaceAll<br /> Collections.replaceAll(list,"412","new");<br /> System.out.println(list);<br /> }<br />}

面试题

collection 和collections的区别