day16.Map集合
课前回顾:1.LinkedList:特点:有序 元素可重复 有索引数据结构:双向链表方法:有很多直接操作首尾元素的方法2.增强for:遍历集合或者数组->foreach格式:for(元素类型 变量名:集合名或者数组名){变量名就代表的是每一个元素}注意:使用增强for的时候,不要随意改变集合长度当用增强for遍历集合时,实现原理是迭代器当用增强for遍历数组时,实现原理就是普通的for3.集合工具类:Collections方法:shuffle(集合)->打乱元素顺序sort(集合)->排序sort(集合,比较器)->按照指定的规则排序4.比较器:ComparableComparator5.泛型:定义类: public class 类名<E>{}定义方法: 修饰符 <E> 返回值类型 方法名(E e){}定义接口: public interface 接口名<E>{}6.Set接口Set接口中的方法没有对Collection中的方法进行扩充7.HashSet特点:无序无索引元素不可重复数据结构:哈希表jdk8之前:哈希表 = 数组+链表jdk8之后:哈希表 = 数组+链表+红黑树加入红黑树原因:查找快方法:底层都是依靠HashMap实现的8.LinkedHashSet:特点:有序无索引元素不可重复数据结构:链表+哈希表9.哈希值:需要调用hashCode方法计算出来的一个十进制数内容不一样,哈希值有可能一样内容一样,哈希值一定一样的10.HashSet存储元素的过程(和今天要学的HashMap是一样的)先比较元素的哈希值,再比较元素的内容如果哈希值不一样,直接存如果哈希值一样,再比较内容如果内容不一样,也存如果内容一样,哈希值也一样,后面的把前面的覆盖掉11.注意:存储的时候比较的哈希值(是内容的哈希值) 内容(就是内容)所以,存储到set集合中保证元素唯一的话,需要重写hashCode和equals方法今日重点:1.Map的全部2.知道HashMap和HashTable的区别3.会使用Properties集合4.会集合嵌套5.了解哈希表的存储过程和细节6.会操作TreeSet和TreeMap
第一章.Map集合
![day16[Map集合] - 图1](https://i.loli.net/2021/06/13/tZrKNub7esxynYc.png#id=Pje3C&originHeight=638&originWidth=1147&originalType=binary&ratio=1&status=done&style=none)
1.Map的介绍
1.概述:双列集合的接口2.特点:元素都是key value形式(键值对)存储 ->一个key 对应一个valuekey唯一 (将key去重复,过程和HashSet一样)无序没有索引3.元素形式:map.put("文章","马伊琍")map.put("文章","姚笛")->和上面的key重复,会将上面的键值对覆盖
2.HashMap的介绍以及使用
1.HashMap 实现了 Map接口2.特点:元素都是key value形式(键值对)存储 ->一个key 对应一个valuekey唯一 (将key去重复,过程和HashSet一样)无序没有索引3.数据结构:哈希表jdk8之前:哈希表 = 数组+链表jdk8之后:哈希表 = 数组+链表+红黑树4.方法:- public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。- public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。- public V get(Object key) 根据指定的键,在Map集合中获取对应的值。- public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。- public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。- public boolean containsKey(Object key):判断该集合中是否有此键。- public Collection<V> values() 返回Map集合中的所有值到Collection集合。
public class Test01 {public static void main(String[] args) {//创建HashMap集合HashMap<String, String> hashMap = new HashMap<>();//- public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。hashMap.put("郭靖","黄蓉");hashMap.put("杨过","小龙女");hashMap.put("张无忌","赵敏");hashMap.put("张翠山","殷素素");hashMap.put("张无忌","周芷若");System.out.println(hashMap);//- public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。/* String value1 = hashMap.remove("郭靖");System.out.println(value1);System.out.println(hashMap);*///- public V get(Object key) 根据指定的键,在Map集合中获取对应的值。String value2 = hashMap.get("杨过");System.out.println(value2);//- public boolean containsKey(Object key):判断该集合中是否有此键。boolean result01 = hashMap.containsKey("杨过");System.out.println(result01);//- public Collection<V> values() 返回Map集合中的所有值到Collection集合。Collection<String> collection = hashMap.values();System.out.println(collection);}}
3.HashMap的两种遍历方式
3.1方式一:获取key,然后根据key获取值
- public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
public class Test02 {public static void main(String[] args) {HashMap<String, String> hashMap = new HashMap<>();hashMap.put("郭靖","黄蓉");hashMap.put("杨过","小龙女");hashMap.put("张无忌","赵敏");hashMap.put("张翠山","殷素素");hashMap.put("涛哥","柳岩");/*将map中的所有key获取出来,存到set集合中*/Set<String> set = hashMap.keySet();/*遍历set集合,将key都遍历出来然后调用map中的get(key)->根据key获取对应的value*/for (String key : set) {String value = hashMap.get(key);System.out.println(key+"..."+value);}}}
3.2方式二:同时获取key和value
![day16[Map集合] - 图2](https://i.loli.net/2021/06/13/O4u7MFboxrQPpqi.png#id=q6w4Q&originHeight=648&originWidth=1273&originalType=binary&ratio=1&status=done&style=none)
- public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象,放到set集合中(Set集合)。
public class Test03 {public static void main(String[] args) {HashMap<String, String> hashMap = new HashMap<>();hashMap.put("郭靖","黄蓉");hashMap.put("杨过","小龙女");hashMap.put("张无忌","赵敏");hashMap.put("张翠山","殷素素");hashMap.put("涛哥","柳岩");/*将记录着键值对的Map.Entry对象获取出来,放到set集合中*/Set<Map.Entry<String, String>> set = hashMap.entrySet();/*将Map.Entry从set中遍历出来遍历出来之后,就可以用getKey getValue*/for (Map.Entry<String, String> entry : set) {String key = entry.getKey();String value = entry.getValue();System.out.println(key+"="+value);}}}
5.HashMap存储自定义对象
1.如何保证key唯一呢?重写hashCode和equals方法
public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age &&Objects.equals(name, person.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}}
public class Test04 {public static void main(String[] args) {HashMap<Person, String> hashMap = new HashMap<>();hashMap.put(new Person("涛哥",18),"廊坊");hashMap.put(new Person("柳岩",36),"湖南");hashMap.put(new Person("涛哥",18),"北京");System.out.println(hashMap);}}
6.Map的练习
需求:利用HashMap集合统计字符串中字符出现的次数步骤:1.创建HashMap集合 key为String value为Integerkey代表字符 value代表字符在字符串中出现的次数2.遍历字符串,将每一个字符都获取出来3.在遍历的过程中,拿着字符去判断,判断map集合中是否包含遍历出来的字符4.如果不包含,直接将字符存进去,value存为15.如果包含,将字符对应的value获取出来,对value进行加1,再将字符和加完之后的value重新存到map中6.输出
![day16[Map集合] - 图3](https://i.loli.net/2021/06/13/1IT9cUbEqSth6NB.png#id=OsNo9&originHeight=616&originWidth=1490&originalType=binary&ratio=1&status=done&style=none)
public class Test05 {public static void main(String[] args) {/*1.创建HashMap集合 key为String value为Integerkey代表字符 value代表字符在字符串中出现的次数*/HashMap<String, Integer> hashMap = new HashMap<>();//2.遍历字符串,将每一个字符都获取出来Scanner sc = new Scanner(System.in);System.out.println("请你输入一个字符串:");String data = sc.next();char[] chars = data.toCharArray();for (char aChar : chars) {String key = aChar+"";//3.在遍历的过程中,拿着字符去判断,判断map集合中是否包含遍历出来的字符if (!hashMap.containsKey(key)){//4.如果不包含,直接将字符存进去,value存为1hashMap.put(key,1);}else{//5.如果包含,将字符对应的value获取出来,对value进行加1,再将字符和加完之后的value重新存到map中Integer value = hashMap.get(key);value++;hashMap.put(key,value);}}//6.输出System.out.println(hashMap);}}
7.LinkedHashMap
1.概述: LinkedHashMap 是 HashMap的子类2.特点:key唯一无索引有序3.数据结构:链表+哈希表4.其他的方面和HashMap一模一样
public class Test06 {public static void main(String[] args) {LinkedHashMap<String, String> map = new LinkedHashMap<>();map.put("张无忌","赵敏");map.put("张学良","于凤至");map.put("张翠山","殷素素");map.put("杨逍","纪晓芙");System.out.println(map);}}
第二章.TreeSet
1.概述: 是Set接口的实现类2.特点:无索引元素唯一对元素进行排序3.数据结构:基于红黑树实现的4.构造:TreeSet() -> 对元素进行自然排序TreeSet(Comparator<? super E> comparator) -> 利用比较器对元素指定排序规则
public class Demo01TreeSet {public static void main(String[] args) {TreeSet<String> treeSet = new TreeSet<>();treeSet.add("2");treeSet.add("1");treeSet.add("3");treeSet.add("4");System.out.println(treeSet);System.out.println("==========================");TreeSet<Person> set = new TreeSet<>(new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge()-o2.getAge();}});set.add(new Person("柳岩",36));set.add(new Person("涛哥",18));set.add(new Person("杨幂",32));System.out.println(set);}}
public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age &&Objects.equals(name, person.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}}
第三章.TreeMap
1.概述: TreeMap是Map接口的实现类2.特点:key唯一对key进行排序无索引3.数据结构:红黑树4.构造:TreeMap() -> 对key进行自然排序TreeMap(Comparator<? super K> comparator) ->对key按照指定规则进行排序
public class Test01 {public static void main(String[] args) {TreeMap<String, Integer> treeMap = new TreeMap<>();treeMap.put("b",2);treeMap.put("a",1);treeMap.put("d",4);treeMap.put("c",3);System.out.println(treeMap);System.out.println("=====================");TreeMap<Person, String> treeMap1 = new TreeMap<>(new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge()-o2.getAge();}});treeMap1.put(new Person("柳岩",36),"湖南");treeMap1.put(new Person("涛哥",16),"河北");treeMap1.put(new Person("杨幂",32),"北京");System.out.println(treeMap1);}}
public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age &&Objects.equals(name, person.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}}
第四章.HashTable和Vector集合(了解)
1.HashTable集合
1.介绍:Map接口的实现类Hashtable, Hashtable类诞生于JDK1.0版本, Map接口诞生于JDK1.2版本. Hashtable类从JDK1.2开始,改进为实现Map接口2.Hashtable类的特点a.底层数据结构是哈希表b.线程安全的,运行速度慢,被更加先进的HashMap取代c.不允许null值,null键, 存储null直接抛出空指针异常d.key唯一,无索引,无序3.和HashMap的区别HashMap:线程不安全的,速度快,可以存储null键null值HashTable:线程安全的,速度慢,不可以存储null键null值,否则会出现空指针异常
2.Vector集合
1.介绍:List接口的实现Vector,命运和Hashtable一样.2.Vector类的特点a.底层实现结构是数组b.数组的默认容量是10,每次扩容是原来的长度*2c.线程安全,运行速度慢,被ArrayList取代
第五章.Properties集合(属性集)
1.Properties 继承 HashTable2.特点:- 继承Hashtable,底层数据结构是哈希表。- 线程安全,运行速度慢。- 不允许null值,null键。- 此集合存储键值对数据类型固定为String。- 可以和IO流结合使用,从流中加载数据。- 无序,无索引,key唯一3.方法:- Object setPropery(String key,String value),向集合中存储键值对。- String getProperty(String key),获取集合中键对应的值,无此键返回null。- Set<String> stringPropertyNames(),集合中的所有键存储到Set集合。- void load(输入流对象) ,IO部分讲解。
public class Test01 {public static void main(String[] args) {Properties properties = new Properties();//- Object setPropery(String key,String value),向集合中存储键值对。properties.setProperty("username","root");properties.setProperty("password","1234");System.out.println(properties);//- String getProperty(String key),获取集合中键对应的值,无此键返回null。String username = properties.getProperty("username");String password = properties.getProperty("password");System.out.println(username+"..."+password);//- Set<String> stringPropertyNames(),集合中的所有键存储到Set集合。System.out.println("======================");Set<String> set = properties.stringPropertyNames();for (String key : set) {System.out.println(properties.getProperty(key));}}}
使用场景:一般都是和properties配置文件结合使用
第六章.集合嵌套
1.List嵌套List
需求:创建3个List集合,每个集合中分别存储一些字符串,将3个List集合存储到第4个List集合中。
public class Demo01ListInList {public static void main(String[] args) {ArrayList<String> list1 = new ArrayList<>();list1.add("孙悟空");list1.add("猪八戒");list1.add("沙和尚");ArrayList<String> list2 = new ArrayList<>();list2.add("宋江");list2.add("林冲");list2.add("武松");ArrayList<String> list3 = new ArrayList<>();list3.add("刘备");list3.add("关羽");list3.add("张飞");//创建一个专门存储集合的ListArrayList<ArrayList<String>> list = new ArrayList<>();list.add(list1);list.add(list2);list.add(list3);//遍历//先遍历大集合,将每一个小集合遍历出来for (ArrayList<String> arrayList : list) {//在遍历每一个小集合,将元素从小集合中获取出来for (String s : arrayList) {System.out.println(s);}}}}
2.List嵌套Map
1班级有第三名同学,学号和姓名分别为:1=张三,2=李四,3=王五,2班有三名同学,学号和姓名分别为:1=黄晓明,2=杨颖,3=刘德华,4=朱丽倩,请将同学的信息以键值对的形式存储到2个Map集合中,在将2个Map集合存储到List集合中。
public class Demo02ListInMap {public static void main(String[] args) {HashMap<Integer, String> map1 = new HashMap<>();map1.put(1,"张三");map1.put(2,"李四");HashMap<Integer, String> map2 = new HashMap<>();map2.put(1,"黄晓明");map2.put(2,"杨颖");ArrayList<HashMap<Integer, String>> list = new ArrayList<>();list.add(map1);list.add(map2);//遍历//先遍历List将每个小Map遍历出来for (HashMap<Integer, String> map : list) {//再遍历每一个map,将map中的键值对获取出来Set<Map.Entry<Integer, String>> entries = map.entrySet();for (Map.Entry<Integer, String> entry : entries) {System.out.println(entry.getKey()+"..."+entry.getValue());}}}}
3.Map嵌套Map
- JavaSE 集合 存储的是 学号 键,值学生姓名- 1 张三- 2 李四- JavaEE- 1 王五- 2 赵柳
public class Demo03MapInMap {public static void main(String[] args) {HashMap<Integer, String> map1 = new HashMap<>();map1.put(1,"张三");map1.put(2,"李四");HashMap<Integer, String> map2 = new HashMap<>();map2.put(1,"王五");map2.put(2,"赵六");HashMap<String, HashMap<Integer, String>> hashMap = new HashMap<>();hashMap.put("JavaSE",map1);hashMap.put("JavaEE",map2);//遍历//先遍历大Map,将每一个小Map遍历出来Set<String> set = hashMap.keySet();for (String key : set) {HashMap<Integer, String> map = hashMap.get(key);//遍历小map,将小map中的key和value遍历出来Set<Map.Entry<Integer, String>> entrySet = map.entrySet();for (Map.Entry<Integer, String> entry : entrySet) {Integer key1 = entry.getKey();String value1 = entry.getValue();System.out.println(key1+"..."+value1);}}}}
第七章.哈希表结构存储过程
![day16[Map集合] - 图4](https://i.loli.net/2021/06/13/es1U2Yzy6RAJmkZ.png#id=ft9eP&originHeight=659&originWidth=1583&originalType=binary&ratio=1&status=done&style=none)
1.HashMap底层数据数据结构:哈希表2.jdk7:哈希表 = 数组+链表jdk8:哈希表 = 数组+链表+红黑树3.先算哈希值,此哈希值在HashMap底层经过了特殊的计算得出如果哈希值不一样,直接存如果哈希值一样,再去比较内容,如果内容不一样,也存如果哈希值一样,内容也一样,直接去重复(后面的value将前面的value覆盖)哈希值一样->哈希冲突(哈希碰撞)4.要知道的点:a.在不指定长度时,哈希表中的数组默认长度为16,HashMap创建出来,一开始没有创建长度为16的数组b.什么时候创建的长度为16的数组呢?在第一次put的时候,底层会创建长度为16的数组c.哈希表中有一个数据加[加载因子]->默认为0.75->代表党元素存储到百分之75的时候要扩容了->2倍d.如果对个元素出现了哈希值一样,内容不一样时,就会在同一个索引上以链表的形式存储,当链表长度>8并且当前数据长度>64时,链表就会改成使用红黑树存储e.加入红黑树目的:查询快
外面笔试时可能会问到的变量default_initial_capacity:HashMap默认容量 16default_load_factor:HashMap默认加载因子 0.75threshold:扩容的临界值 等于 容量*0.75 = 12treeify_threshold:链表长度默认值,转为红黑树:8min_treeify_capacity:链表被树化时最小的数组容量:64
第八章.哈希表源码分析
1.HashMap无参数构造方法的分析
//HashMap中的静态成员变量static final float DEFAULT_LOAD_FACTOR = 0.75f;public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}
解析:使用无参数构造方法创建HashMap对象,将加载因子设置为默认的加载因子,loadFactor=0.75F。
2.HashMap有参数构造方法分析
HashMap(int initialCapacity, float loadFactor) ->创建Map集合的时候指定底层数组长度以及加载因子public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);//10}
解析:带有参数构造方法,传递哈希表的初始化容量和加载因子
- 如果initialCapacity(初始化容量)小于0,直接抛出异常。
- 如果initialCapacity大于最大容器,initialCapacity直接等于最大容器
- MAXIMUM_CAPACITY = 1 << 30 是最大容量 (1073741824)
- 如果loadFactor(加载因子)小于等于0,直接抛出异常
- tableSizeFor(initialCapacity)方法计算哈希表的初始化容量。
- 注意:哈希表是进行计算得出的容量,而初始化容量不直接等于我们传递的参数。
3.tableSizeFor方法分析
static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}8 4 2 1规则->无论指定了多少容量,最终经过tableSizeFor这个方法计算之后,都会遵循8421规则去初始化列表容量
解析:该方法对我们传递的初始化容量进行位移运算,位移的结果是 8 4 2 1 码
- 例如传递2,结果还是2,传递的是4,结果还是4。
- 例如传递3,结果是4,传递5,结果是8,传递20,结果是32。
4.Node 内部类分析
哈希表是采用数组+链表的实现方法,HashMap中的内部类Node非常重要,证明HashSet是一个单向链表
static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}
解析:内部类Node中具有4个成员变量
- hash,对象的哈希值
- key,作为键的对象
- value,作为值得对象(讲解Set集合,不牵扯值得问题)
- next,下一个节点对象
5.存储元素的put方法源码
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}
解析:put方法中调研putVal方法,putVal方法中调用hash方法。
- hash(key)方法:传递要存储的元素,获取对象的哈希值
- putVal方法,传递对象哈希值和要存储的对象key
6.putVal方法源码
Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;
解析:方法中进行Node对象数组的判断,如果数组是null或者长度等于0,那么就会调研resize()方法进行数组的扩容。
7.resize方法的扩容计算
if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}
解析:计算结果,新的数组容量=原始数组容量<<1,也就是乘以2。
8.确定元素存储的索引
if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);
解析:i = (数组长度 - 1) & 对象的哈希值,会得到一个索引,然后在此索引下tab[i],创建链表对象。
不同哈希值的对象,也是有可能存储在同一个数组索引下。
其中resize()扩容的方法,默认是16tab[i] = newNode(hash, key, value, null);->将元素放在数组中 i就是索引i = (n - 1) & hash0000 0000 0000 0000 0000 0000 0000 1111->15& 0&0=0 0&1=0 1&1=10000 0000 0000 0001 0111 1000 0110 0011->96355--------------------------------------------------------0000 0000 0000 0000 0000 0000 0000 0011->3
0000 0000 0000 0000 0000 0000 0000 1111->15& 0&0=0 0&1=0 1&1=10000 0000 0001 0001 1111 1111 0001 0010->1179410--------------------------------------------------------0000 0000 0000 0000 0000 0000 0000 0010->2
9.遇到重复哈希值的对象
Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;
解析:如果对象的哈希值相同,对象的equals方法返回true,判断为一个对象,进行覆盖操作。
else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}
解析:如果对象哈希值相同,但是对象的equals方法返回false,将对此链表进行遍历,当链表没有下一个节点的时候,创建下一个节点存储对象。
