zoukankan      html  css  js  c++  java
  • java集合总结【转】

    Map、Set、Iterator迭代详解

    Map接口定义了四种类型的方法,每个Map都包含这些方法。

    equals(Object o)比较指定对象与此Map的等价性。

    hashCode()返回此Map的哈希码。

    Map定义了几个用于插放和删除元素的变换方法。

    remove(Object key) 从Map中删除键和关联的值。

    put(object key,Object value) 将指定值与指定键相关联。

    clear() 从Map虽删除所有映射。

    putAll(Map t) 将指定Map中的所有映射复制到此Map。

    查看Map

    迭代Map中的元素不存在直接了当的方法。假如要查询某个Map以了解其哪些元素满足特定查询或假如要迭代其所有元素,则您首先需要获取该Map的“视图”。有三种可能的视图:所有

    键值对、所有键、所有值。前两个视图均返回Set对象,第三个视图返回Collection对象。就这两种情况而言,问题到这里并没有结束,这是因为您无法直接迭代Collection对象或Set对

    象。要进行迭代,您必须获得一个Iterator对象。因此要迭代Map的元素,必须进行如下编码:

    Iterator keyValuePairs = aMap.entrySet().iterator();

    Iterator keys=aMap.keySet().iterator();

    Iterator values=aMap.values().iterator();

    值得注意的是,这些对象实际上是基础Map的视图,而不是包含所有元素的副本。

    使用Iterator对象,您可以遍历Map的元素,还可以删除Map中的元素。

    EnterySet()返回Map中所包含映射的Set视图。Set中的每个元素都是一个Map.Entry对象,可以使用getKey()和getValue()方法访问后者的键元素和值元素。

    keySet()返回Map中所包含键的Set视图。删除Set中的元素还将删除Map中相应原映射。

    values()返回Map中所包含值的Collection视图。删除Collection中的元素还将删除Map中相应的映射

    Map访问和测试方法:

    get(Object key) 返回与指定键关联的值

    containsKey(Object key) 假如Map包含指定键的映射,则返回True

    isEmpty() 假如Map不包含键-值映射,则返回True

    size() 返回Map中的键值映射的数目。

    Set(interface):存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。

    Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。

    迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

    Java中的Iterator功能比较简单,并且只能单向移动:使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。使用next()获得序列中的

    下一个元素。使用hasNext()检查序列中是否还有元素。使用remove()将迭代器新返回的元素删除。

    Java平台的集合框架

    “集合框架”主要由一组用来操作对象的接口组成。不同接口描述一组不同数据类型。

    集合接口:6个接口,表示不同集合类型,是集合框架的基础。

    抽象类:5个抽象类,对集合接口的部分实现。可扩展为自定义集合类。

    实现类:8个实现类,对接口的具体实现。

    在很大程度上,一旦您理解了接口,您就理解了框架。虽然您总要创建接口特定的实现,但访问实际集合的方法应该限制在接口方法的使用上;

    因此,允许您更基本的数据结构而不必改变其它代码。

    Collection接口是一组允许重复的对象。

    Set接口继承Collection,但不允许重复,使用自己内部的排列机制。

    List接口继承Collection,允许重复,以元素安插的次序来放置元素,不会重新排列。

    Map接口是一组成对的键-值对象。Map中不能有重复的Key.拥有自己的内部排列机制。

    容器中的元素类型都为Object。从容器取得元素时,必须把它转换成原来的类型。

    集合接口:

    Collection接口:用于表示任何对象或元素组。想要尽可能以常规方式处理一组元素时,就使用这一接口。

    1、单元素添加、删除操作:

    Boolean add(Object o):将对象添加到集合

    Boolean remove (Object o):如果集体中有与O相匹配的对象,则删除对象O。

    2、查询操作:

    Int size():返回当前集合中元素的数量

    Boolean isEmpty():判断集合中是否有任何元素。

    Boolean contains(Object o):查找集合中是否否有对象O

    Iterator iterator():返回一个迭代器,用来访问集合中的各个元素。

    3、组操作:作用于元素组或整个集合

    Boolean containsAll(Collection c):查找集合中是否含有集合C中所有元素

    Boolean addAll(Collection c):将集合C中所有元素添加到该集合。

    Void clear():删除集合中所有元素。

    Void removeAll(Collection c):从集合中删除集合C中的所有元素。

    Void retainAll(Collection c):从集合中删除集合C中不包含的元素

    4、Collection转换为Object数组:

    Object[] toArray():返回一个内含集合所有元素的Array

    Object[] toArray(Object[] a):返回一个内含集合所有元素的Array.运行期返回的Array和参数A的型别相同,需要转换为正确型别。

    此外,您还可以把集合转换成其它任何其它的对象数组。但是,您不能直接把集合转换成基本数据类型的数组,因为集合必须持有对象。Collection不提供get()方法。如果要遍历

    Collection中的元素,就必须用Iterator.

    AbstractCollection抽象类

    AbstactCollection类提供具体“集合框架”类的基本功能。虽然您可以自实现Collection接口的所有方法,但是,除了Iterator()和Size()方法在恰当的子类中实现以外,其它所有方法都由

    AbstractCollection类来提供实现。如果子类不覆盖某些方法,可选的方法将抛出异常。

    Iterator接口

    Collection接口的Iterator()方法返回一个Iterator。Iterator接口方法能以迭代方式逐个访问集合中各个元素,并安全的从Collection中除去适当的元素。

    1、Boolean hasNext():判断是否存在另一个可访问的元素。

    2、Object next():返回要访问的下一个元素。如果到达集合结尾,则抛出NoSuchElementException异常。

    3、Void remove():删除上次访问返回的对象。本方法必须紧跟在一个元素的访问后执行。如果上次访问后集合已被修改,方法将抛出

    IIIegalStateException。

    Iterator中删除操作对底层Collection也有影响。

    迭代器是故障快速修复的。这意味着,当另一个线程修改底层集合的时候,如果您正在用Iterator遍历集合,那么,Iterator就会抛出

    ConcurrentModificationException异常并立刻失败。

    List接口继承了Collection接口以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。

    1、面向位置的操作包括插入某个元素,还包括获取、除去或更改元素的功能。在List中搜索元素可以从列表的头部或尾部开始,如果找到元素,还将报告元素所在的位置:

    Void add(int index,Object element):在指定位置Index上添加元素element

    Boolean addAll(int index,Collection c):将集合C的所有元素添加到指定位置Index

    Object get(int index):返回List中指定位置的元素

    Int indexOf(Object o):返回第一个出现元素O的位置,否则返回-1

    Int lastIndexOf(Object o):返回最后一个出现元素O的位置,否则返回-1

    Object remove(int index):删除指定位置上的元素

    Object set(int index,Object element):用元素Element取代位置Index上的元素,并且返回旧的元素

    2、List接口不但以位置序列迭代的遍历整个列表,还能处理集合的子集:

    ListIterator ListIterator():返回一个列表迭代器,用来访问列表中的元素

    ListIterator ListIterator(int index):返回一个列表迭代器,用来从指定位置Index开始访问列表中的元素

    List subList(int formIndex,int toIndex):返回从指定位置FromIndex(包含)到ToIndex(不包含)范围中各个元素的列表视图对子列表的更改对底层List也有影响。

    ListIterator接口继承Iterator接口以支持添加或更改底层集体中的元素,还支持双向访问。ListIterator没有当前位置,光标位于调用

    Previous和Next方法返回的值之间。一个长度为N的列表,有N+1个有效索引值:

    1、Void add(Object o):将对象O添加到当前位置的前面

    Void set(Object o):用对象O替代Next或Previous方法访问的上一个元素。如果上次调用后列表结构被修改了,那么将抛出IIIegalStateException异常。

    2、Boolean hasPrevious():判断向后迭代时是否有元素可访问

    Object previous():返回上一个对象

    Int nextIndex():返回下次调用Next方法时将返回的元素的索引

    Int previousIndex():返回下次调用Previous方法时将返回的元素的索引

    有两个抽象的List实现类:AbstractList和AbstractSequentialList。它们覆盖了Equals()和HashCode()方法以确保两个相等的集合返回相同的哈希码。

    在“集合框架”中有两种常规的List实现:ArrayList和LinkedList。如果要支持随机访问,而不必在除尾部的任何位置插入或除去元素,那么,ArrayList提供了可选的集合。但如果,您要

    频繁的从列表的中间位置添加和除去元素,而只要顺序的访问列表元素,那么,LinkedList实现更好。

    ArrayList和LinkedList都实现Cloneable接口,都提供了两个构造函数,一个无参的,一个接受另一个Collection。

    LinkedList类添加了一些处理列表两端元素的方法。

    1、Void addFirst(Object o):将对象O添加到列表的开头。

    Void addLast(Object o):将对象O添加到列表的结尾

    2、Object getFirst():返回列表开关的元素

    Object getLast():返回列表结尾的元素

    3、Object removeFirst():删除并且返回列表开关的元素

    Object removeLast():删除并且返回列表结尾的元素

    4、LinkedList():构建一个空的链接列表

    Linked(Collection c):构建一个链接列表,并且添加集合C的所有元素使用这些新方法,您就可以轻松的把LinkedList当作一个堆栈、队列或其它面向端点的数据结构


    ArrayList类封装了一个动态再分配的Object[]数组。每个ArrayList对象有一个Capacity。这个Capacity表示存储列表中元素的数组的容量。当元素添加到ArrayList时,它的Capacity在常量

    时间内自动增加。在向一个ArrayList对象添加大量元素的程序中,可使用ensureCapacity方法增加Capacity.这可以减少增加重分配的数量。

    1、Void ensureCapacity(int minCapacity):将ArrayList对象容量增加MinCapacity

    2、Void trimToSize():整理ArrayList对象容量为列表当前大小。程序可使用这个操作减少ArrayList对象存储空间。

    Set接口继承Collection接口,而且它不允许集合中存在重复项,每个具体的Set实现类依赖添加的对象的Equals()方法来检查狂性。Set接口没有引入新方法,所以Set就是一个

    Collection,只不过其行为不同。

    Hash表是一种数据结构,用来查找对象。Hash表为每个对象计算出一个整数,称为Hash Code(哈希码)。Hash表是个链接式列表的阵列。每个列表称为一个Buckets(哈希表元)。

    对象位置的计算Index=HashCode%buckets。

    在“集合框架”中有两种比较接口:Comparable接口和Comparator接口。像String和Integer等Java内建类实现Comparable接口以提供一定排序方式,但这样只能实现该接口一次。对

    于那些没有实现Comparable接口的类、或者自定义的类,您可以通过Comparator接口来定义您自己的比较方式。

    在Java.lang包中,Comparable接口适用于一个类有自然顺序的时候。假定对象集合是同一个类型,该接口允许您把集合排序成自然顺序。

    Int compareTo(Object o):比较当前实例对象与对象O,如果位于对象O之前,返回负值,如果两个对象在排序中位置相同,则返回0,如果位于对象O后面,则返回正值。

    SortedSet,它保持元素的有序顺序。SortedSet接口为集合的视图和它的两端提供了访问方法。当佻处理列表的子集时,理性视图会反映到源集。此外,更改源集也会反映在子集上。发

    生这种情况的原因在于视图由两端的元素而不是下标元素指定,所以如果您想要一个特殊的高端元素在子集中,您必须找到下一个元素。添加到SortedSet实现类的元素必须实现

    Comparable接口,否则您必须给它的构造函数提供一个接口的实现。TreeSet类是它的唯一一分实现。

    1、Comparator comparator():返回对元素进行排序时使用的比较器,如果使用Comparable接口的compareTo()方法对元素进行比较,则返回Null。

    2、Object first():返回有序集合中第一个元素。

    3、Object last():返回有序集合中最后一个元素。

    4、SortedSet subSet(Object fromElement,Object toElement):返回从formElement到ToElement范围内元素的SortedSet视图。

    5、SortedSet headSet(Object toElemtn):返回SortedSet的一个视图,其内各元素皆小于Toelement

    6、SortedSet tailSet(Object fromElement):返回SortedSet的一个视图,其内各元素皆大于或等于FromElement

    AbstractSet类覆盖了Object类的equals()和hashCode()方法,以确保两个相等的集返回相同的哈希码。若两个集大小相等且包含相同元素,则这两个集相等。按定义,集的哈希码是集中

    元素哈希码的总和。因此,不论集的内部顺序如何,两个相等的集合有相同的哈希码。

    Object类

    1、Boolean equals(Object obj):对两个对象进行比较,以便确定它们是否相同

    2、Int hashCode():返回该对象的哈希码。相同的对象必须返回相同的哈希码

    “集合框架”支持Set接口两种普通的实现:HashSet和TreeSet。HashSet存储重复自由的集合。当您要从集合中以有序的方式插入和抽取元素时,TreeSet实现会有用处。为了能顺利进

    行,添加到TreeSet的元素必须是可排序的。

    HashSet类

    1、HashSet():构建一个空的哈希集

    2、HashSet(Collection c):构建一个哈希集,并且添加集合C中所有元素

    3、HashSet(int initialCapacity):构建一个拥有特定容量的空哈希集

    4、HashSet(int initialCapacity,float loadFactor):构建一个拥有特定容量和加载因子的空哈希集。LoadFactor是0至1之间的一个数

    TreeSet类

    1、TreeSet():构建一个空的树集

    2、TreeSet(Collection c):构建一个树集,并且添加集合C中所有元素

    3、TreeSet(Comparator c):构建一个树集,并且使用特定的比较器对其元素进行排序

    4、TreeSet(SortedSet S):构建一个树集,添加有序集合S中所有元素,并且使用与有序集合S相同的比较器排序。

    LinkedHashSet的迭代器按照元素的插入顺序来访问各个元素。它提供了一个可以快速访问各个元素的有序集合。同时,它也增加了实现的代价,因为哈希表元中的各个元素是通过双重

    链接式列表链接在的。

    1、LinkedHashSet():构建一个空的链接式哈希集

    2、LinkedHashSet(Collection c):构建一个链接式哈希集,并且添加集合C中所有元素

    3、LinkedHashSet(int initialCapacity):构建一个拥有特定容量的空链接式哈希集

    4、LinkedHashSet(int initialCapacity,float loadFactor):构建一个拥有特定容量和加载因子的空链接式哈希集。LoadFactor是0至1之间的一个数。

    Map接口用于维护键/值对。该接口描述了从不重复的键到值的映射

    1、添加、删除操作

    Object put(Object key,Object value):将互相关联的一个关键字与一个值放入该映像。如果该关键字已经存在,那么与此关键字相关的新值

    将取代旧值。方法返回关键字的旧值,如果关键字原先并不存在,则返回Null

    Object remove(Object key):从映像中删除与Key相关的映射

    Void putAll(Map t):将来自特定映像的所有元素添加到该映像

    Void clear():从映像中删除所有映射

    2、查询操作:

    Object get(Object key):获得与关键字Key相关的值,并且返回与关键字Key相关的对象,如果没有在该映像中找到该关键字,则返回Null

    Boolean containsKey(Object key):判断映像中是否存在关键字Key

    Boolean containsValue(Object value):判断映像中是否存在值Value

    Int size():返回当前映像中映射的数量

    Boolean isEmpty():判断映像中是否有任何映射

    3、视图操作:处理映像中键/值对组

    Set keySet():返回映像中所有关键字的视图集

    Collection values():返回映像中所有值的视图集

    Set entrySet():返回Map.Entry对象的视图集,即映像中的关键字/值对

    Map的entrySet()方法返回一个实现Map.Entry接口的对象集合。集合中每个对象都是底层Map中一个特定的键/值对。通过这个集合的迭代器,您可以获得每一个条目的键或值并对值进行更

    改。当条目通过迭代器返回后,除非是迭代器自身的remove()方法或者迭代器返回的条目的setValue()方法,其余对源Map外部的修改都会导致此条目集变得无效,同时产生条目行为未定

    义。

    Object getKey():返回条目的关键字

    Object getValue():返回条目的值

    Obect setValue(Object value):将相关映像中的值改为value,并且返回旧值。

         “集合框架”提供了个特殊的Map接口:SortedMap,它用来保持键的有序顺序。SortedMap接口为映像的视图(子集),包括两个端点提供了访问方法。除了排序是作用于映射的键以

    外,处理SortedMap和处理SortedSet一样。添加到SortedMap实现类的元素必须实现Comparable接口,否则您必须给它的构造函数提供一个Comparator接口的实现。TreeMap类是它的

    唯一一份实现。

      “因为对于映射来说,每个键只能对应一个值,如果在添加一个键/值对时比较两个键产生了0返回值(通过Comparable的compareTo()方法或通过Comparator的compare()方法),

    那么,原始键对应值被新的值替代。如果两个元素相等,那还好。但如果不相等,那么您就应该修改比较方法,让比较方法和 equals() 的效果一致。”

      (1) Comparator comparator(): 返回对关键字进行排序时使用的比较器,如果使用Comparable接口的compareTo()方法对关键字进行比较,则返回null

      (2) Object firstKey(): 返回映像中第一个(最低)关键字

      (3) Object lastKey(): 返回映像中最后一个(最高)关键字

      (4) SortedMap subMap(Object fromKey, Object toKey): 返回从fromKey(包括)至toKey(不包括)范围内元素的SortedMap视图(子集)

      (5) SortedMap headMap(Object toKey): 返回SortedMap的一个视图,其内各元素的key皆小于toKey

      (6) SortedSet tailMap(Object fromKey): 返回SortedMap的一个视图,其内各元素的key皆大于或等于fromKey

      4.3. AbstractMap抽象类

      和其它抽象集合实现相似,AbstractMap 类覆盖了equals()和hashCode()方法以确保两个相等映射返回相同的哈希码。如果两个映射大小相等、包含同样的键且每个键在这两个映射

    中对应的值都相同,则这两个映射相等。映射的哈希码是映射元素哈希码的总和,其中每个元素是Map.Entry接口的一个实现。因此,不论映射内部顺序如何,两个相等映射会报告相同的

    哈希码。

      4.4. HashMap类和TreeMap类

      “集合框架”提供两种常规的Map实现:HashMap和TreeMap (TreeMap实现SortedMap接口)。在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序

    或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和equals()的实现。

      这个TreeMap没有调优选项,因为该树总处于平衡状态。

            4.4.1. HashMap类

      为了优化HashMap空间的使用,您可以调优初始容量和负载因子。

      (1) HashMap(): 构建一个空的哈希映像

      (2) HashMap(Map m): 构建一个哈希映像,并且添加映像m的所有映射

      (3) HashMap(int initialCapacity): 构建一个拥有特定容量的空的哈希映像

      (4) HashMap(int initialCapacity, float loadFactor): 构建一个拥有特定容量和加载因子的空的哈希映像

      4.4.2. TreeMap类

      TreeMap没有调优选项,因为该树总处于平衡状态。

      (1) TreeMap():构建一个空的映像树

      (2) TreeMap(Map m): 构建一个映像树,并且添加映像m中所有元素

      (3) TreeMap(Comparator c): 构建一个映像树,并且使用特定的比较器对关键字进行排序

      (4) TreeMap(SortedMap s): 构建一个映像树,添加映像树s中所有映射,并且使用与有序映像s相同的比较器排序

      4.5. LinkedHashMap类

      LinkedHashMap扩展HashMap,以插入顺序将关键字/值对添加进链接哈希映像中。象LinkedHashSet一样,LinkedHashMap内部也采用双重链接式列表。

      (1) LinkedHashMap(): 构建一个空链接哈希映像

      (2) LinkedHashMap(Map m): 构建一个链接哈希映像,并且添加映像m中所有映射

      (3) LinkedHashMap(int initialCapacity): 构建一个拥有特定容量的空的链接哈希映像

      (4) LinkedHashMap(int initialCapacity, float loadFactor): 构建一个拥有特定容量和加载因子的空的链接哈希映像

      (5) LinkedHashMap(int initialCapacity, float loadFactor,

      boolean accessOrder): 构建一个拥有特定容量、加载因子和访问顺序排序的空的链接哈希映像

      “如果将accessOrder设置为true,那么链接哈希映像将使用访问顺序而不是插入顺序来迭代各个映像。每次调用get或者put方法时,相关的映射便从它的当前位置上删除,然后放到链

    接式映像列表的结尾处(只有链接式映像列表中的位置才会受到影响,哈希表元则不受影响。哈希表映射总是待在对应于关键字的哈希码的哈希表元中)。”

      “该特性对于实现高速缓存的“删除最近最少使用”的原则很有用。例如,你可以希望将最常访问的映射保存在内存中,并且从数据库中读取不经常访问的对象。当你在表中找不到

    某个映射,并且该表中的映射已经放得非常满时,你可以让迭代器进入该表,将它枚举的开头几个映射删除掉。这些是最近最少使用的映射。”

      (6) protected boolean removeEldestEntry(Map.Entry eldest): 如果你想删除最老的映射,则覆盖该方法,以便返回true。当某个映射已经添加给映像之后,便调用该方法。它的默认

    实现方法返回false,表示默认条件下老的映射没有被删除。但是你可以重新定义本方法,以便有选择地在最老的映射符合某个条件,或者映像超过了某个大小时,返回true。

      4.6. WeakHashMap类

      WeakHashMap是Map的一个特殊实现,它使用WeakReference(弱引用)来存放哈希表关键字。使用这种方式时,当映射的键在 WeakHashMap的外部不再被引用时,垃圾收集器会

    将它回收,但它将把到达该对象的弱引用纳入一个队列。WeakHashMap的运行将定期检查该队列,以便找出新到达的弱应用。当一个弱引用到达该队列时,就表示关键字不再被任何人

    使用,并且它已经被收集起来。然后WeakHashMap便删除相关的映射。

      (1) WeakHashMap(): 构建一个空弱哈希映像

      (2) WeakHashMap(Map t): 构建一个弱哈希映像,并且添加映像t中所有映射

      (3) WeakHashMap(int initialCapacity): 构建一个拥有特定容量的空的弱哈希映像

      (4) WeakHashMap(int initialCapacity, float loadFactor): 构建一个拥有特定容量和加载因子的空的弱哈希映像

      4.6. IdentityHashMap类

      IdentityHashMap也是Map的一个特殊实现。在这个类中,关键字的哈希码不应该由hashCode()方法来计算,而应该由System.identityHashCode方法进行计算(即使已经重新定义了

    hashCode方法)。这是Object.hashCode根据对象的内存地址来计算哈希码时使用的方法。另外,为了对各个对象进行比较,IdentityHashMap将使用==,而不使用equals方法。

      换句话说,不同的关键字对象,即使它们的内容相同,也被视为不同的对象。IdentityHashMap类可以用于实现对象拓扑结构转换(topology-preserving object graph transformations)

    (比如实现对象的串行化或深度拷贝),在进行转换时,需要一个“节点表”跟踪那些已经处理过的对象的引用。即使碰巧有对象相等,“节点表”也不应视其相等。另一个应用是维护代理

    对象。比如,调试工具希望在程序调试期间维护每个对象的一个代理对象。

      “IdentityHashMap类不是一般意义的Map实现!它的实现有意的违背了Map接口要求通过equals方法比较对象的约定。这个类仅使用在很少发生的需要强调等同性语义的情况。”

      (1) IdentityHashMap (): 构建一个空的全同哈希映像,默认预期最大尺寸为21“预期最大尺寸是映像期望把持的键/值映射的最大数目”

      (2) IdentityHashMap (Map m): 构建一个全同哈希映像,并且添加映像m中所有映射

      (3) IdentityHashMap (int expectedMaxSize): 构建一个拥有预期最大尺寸的空的全同哈希映像。放置超过预期最大尺寸的键/值映射时,将引起内部数据结构的增长,有时可能很费时。

  • 相关阅读:
    Linux常用命令(文件常用命令)
    Spring中的核心思想
    AOP中重要概念
    Spring框架中2种生成代理对象的方法
    python模块详解 | psutil
    Linux学习笔记 | 常见错误之无法获得锁
    python学习笔记 | 列表去重
    CentOS | python3.7安装指南
    selenium爬虫 | 爬取疫情实时动态(二)
    selenium爬虫 | 爬取疫情实时动态
  • 原文地址:https://www.cnblogs.com/fx2008/p/3531020.html
Copyright © 2011-2022 走看看