Fork me on GitHub

遍历集合的方法总结

遍历集合(容器)的方法总结

使用Iterator迭代器遍历容器元素

Iterator迭代器

Java集合框架的集合类,我们有时候称之为容器。容器的种类有很多种,如ArrayList、LinkedList、HashSet…,每种容器都有自己的特点,ArrayList底层维护的是一个数组;LinkedList是链表结构;HashSet依赖的是哈希表,每种容器都有自己特有的数据结构。

因为容器的内部结构不同,很多时候可能不知道该怎样去遍历一个容器中的元素。所以为了使对容器内元素的操作更为简单,Java引入了迭代器模式。

把访问逻辑从不同类型的集合类中抽取出来,从而避免向外部暴露集合的内部结构。

使用Iterator遍历List、Set、Map,代码如下:

package com.msl.collection;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * 测试迭代器遍历集合
 * @author Senley
 *
 */
public class TestIterator {
    public static void main(String[] args) {
        testIteratorList();
        testIteratorSet();
        testIteratorMap();
    }
    
    public static void testIteratorList() {
        List<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        
        //使用Iterator遍历List
        for(Iterator<String> iter=list.iterator();iter.hasNext();) {
            String temp = iter.next();
            System.out.println(temp);
        }
    }
    
    public static void testIteratorSet() {
        Set<String> set = new HashSet<>();
        set.add("aa");
        set.add("bb");
        set.add("cc");
        
        //使用Iterator遍历Set
        for(Iterator<String> iter=set.iterator();iter.hasNext();) {
            String temp = iter.next();
            System.out.println(temp);
        }
    }
    
    public static void testIteratorMap() {
        Map<Integer,String> map = new HashMap<>();
        map.put(100,"aa");
        map.put(200,"bb");
        map.put(300,"cc");
        
        //使用Iterator遍历Map,第一种方式
        Set<Entry<Integer,String>> ss = map.entrySet();
        for(Iterator<Entry<Integer,String>> iter=ss.iterator();iter.hasNext();) {
            Entry<Integer,String> temp = iter.next();
            System.out.println(temp.getKey()+"---"+temp.getValue());
        }
        
        //使用Iterator遍历Map,第二种方式
        Set<Integer> keySet = map.keySet();
        for(Iterator<Integer> iter=keySet.iterator();iter.hasNext();) {
            Integer key = iter.next();
            System.out.println(key+"---"+map.get(key));
        }
    }
}

结果如下:

aa
bb
cc
aa
bb
cc
100---aa
200---bb
300---cc
100---aa
200---bb
300---cc

遍历集合(容器)的方法

遍历List方法一:普通for循环

for(int i=0;i<list.size();i++){//list为集合的对象名
    String temp = (String)list.get(i);
    System.out.println(temp);
}

遍历List方法二:增强for循环

for (String temp : list) {
    System.out.println(temp);
}

遍历List方法三:使用Iterator迭代器(1)

for(Iterator iter = list.iterator();iter.hasNext();){
    String temp = (String)iter.next();
    System.out.println(temp);
}

遍历List方法四:使用Iterator迭代器(2)

Iterator iter = list.iterator();
while(iter.hasNext()){
    Object  obj = iter.next();
    iter.remove();//如果要遍历时,删除集合中的元素,建议使用这种方式
    System.out.println(obj);
}

遍历Set方法一:增强for循环

for(String temp:set){
    System.out.println(temp);
}

遍历Set方法二:使用Iterator迭代器

for(Iterator iter = set.iterator();iter.hasNext();){
    String temp = (String)iter.next();
    System.out.println(temp);
}

遍历Map方法一:根据key获取value

Map<Integer, Man> maps = new HashMap<Integer, Man>();
Set<Integer> keySet = maps.keySet();
for(Integer id : keySet){
    System.out.println(maps.get(id).name);
}

遍历Map方法二:使用entrySet

Set<Entry<Integer, Man>> ss = maps.entrySet();
for (Iterator iterator = ss.iterator(); iterator.hasNext();) {
    Entry e = (Entry) iterator.next(); 
    System.out.println(e.getKey()+"--"+e.getValue());
}

自定义一个HashSet

自定义一个HashSet

HashSet特点和底层实现

  • HashSet底层是HashMap;

  • 向Hashset中添加元素, 实际上是把这个元素作为键添加到底层的HashMap中;

  • HashSet实际上就是底层HashMap的键的集合。

HashSet是采用哈希算法实现,底层实际是用HashMap实现的(HashSet本质就是一个简化版的HashMap),因此,查询效率和增删效率都比较高。查看HashSet的源码:

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }
    
     /**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element <tt>e</tt> to this set if
     * this set contains no element <tt>e2</tt> such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns <tt>false</tt>.
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     * element
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    //以下代码省略
}

可以发现里面有个map属性,这就是HashSet的核心秘密。再看add()方法,可以发现增加一个元素,说白了就是在map中增加一个键值对,键对象就是这个元素,值对象是名为PRESENT的Object对象。说白了就是“往set中加入元素,本质就是把这个元素作为key加入到了内部的map中”。

由于map中key都是不可重复的,因此,Set天然具有“不可重复”的特性。

自定义一个HashSet,体会底层原理

代码如下:

package com.msl.mycollection;

import java.util.HashMap;

/**
 * 手动实现一个HashSet,深刻理解HashSet底层原理
 * @author Senley
 *
 */
public class MslHashSet {
    HashMap map;
    private static final Object PRESENT = new Object();
    
    public static void main(String[] args) {
        MslHashSet set = new MslHashSet();
        set.add("aaa");
        set.add("bbb");
        set.add("ccc");
        System.out.println(set);
    }
    
    public MslHashSet(){
        map = new HashMap();
    }
    
    public int size() {
        return map.size();
    }
    
    public void add(Object o) {
        map.put(o, PRESENT);
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for(Object key:map.keySet()) {
            sb.append(key+",");
        }
        sb.setCharAt(sb.length()-1, ']');
        return sb.toString();
    }
}

结果如下:

[aaa,ccc,bbb]

自定义一个HashMap

自定义一个HashMap

HashMap的结构

在HashMap底层实现原理中,聊过Node<K,V>[]数组的结构,也是HashMap的结构。如下:

Node数组存储结构

自定义一个Node类

package com.msl.mycollection;
/**
 * 用于MslHashMap中
 * @author Senley
 *
 */
public class Node<K,V> {
    int hash;
    K key;
    V value;
    Node next;
}

自定义一个HashMap,体会底层原理

在上述基础上自定义实现一个HashMap,实现如下简单功能:

  1. 实现put方法增加键值对;
  2. 解决键重复问题和链表生成问题;
  3. 实现toString方法,方便查看Map中的键值对信息;
  4. 实现get方法,根据键对象获得对应的值对象;
  5. 增加泛型。

代码如下:

package com.msl.mycollection;
/**
 * 自定义一个HashMap
 * @author Senley
 *
 */
public class MslHashMap<K,V> {
    Node[] table;  //位桶数组。bucket array
    int size;      //存放的键值对的个数
    
    public static void main(String[] args) {
        MslHashMap<Integer,String> m = new MslHashMap<>();
        m.put(10, "aa");
        m.put(20, "bb");
        m.put(30, "cc");
        m.put(20, "ss");
        
        m.put(53, "ee");
        m.put(69, "ff");
        m.put(85, "gg");
        
        System.out.println(m);
        
//        for(int i=10;i<100;i++) {
//            System.out.println(i+"---"+myHash(i,16)); //53,69,85
//        }
        
        System.out.println(m.get(53));
    }
    
    public MslHashMap() {
        table = new Node[16]; //长度一般定义成2的整数幂
    }
    
    @Override
    public String toString() {
        //{10:aa,20:bb}
        StringBuilder sb = new StringBuilder("{");
        //遍历bucket数组
        for(int i=0;i<table.length;i++) {
            Node temp = table[i];
            //遍历链表
            while(temp!=null) {
                sb.append(temp.key+":"+temp.value+",");
                temp = temp.next;
            }
        }
        sb.setCharAt(sb.length()-1, '}');
        return sb.toString();
    }
    
    public void put(K key,V value) {
        //还需要考虑数组扩容问题!
        
        //定义了新的结点对象
        Node newNode = new Node();
        newNode.hash = myHash(key.hashCode(),table.length);
        newNode.key = key;
        newNode.value = value;
        newNode.next = null;
        
        Node temp = table[newNode.hash];
        Node iterLast = null; //正在遍历的最后一个元素
        boolean keyRepeat = false; 
        if(temp == null) {
            //此处数组元素为空,则直接将新结点放进去
            table[newNode.hash] = newNode;
            size++;
        }else {
            //此处数组元素不为空,则遍历对应链表
            while(temp!=null) {
                //判断key如果重复,则覆盖
                if(temp.key.equals(key)) {
                    keyRepeat = true;
                    System.out.println("key重复了");
                    temp.value = value; //只是覆盖value即可,其他的值(hash,key,next)保持不变
                    break;
                }else {
                    //key不重复,则遍历下一个
                    iterLast = temp;
                    temp = temp.next;
                }
            }
            if(!keyRepeat) { //如果没有发生key重复的情况,则添加到链表最后
                iterLast.next = newNode;
                size++;
            }
        }
    }
    
    public V get(K key) {
        int hash = myHash(key.hashCode(),table.length);
        V value = null;
        if(table[hash]!=null) {
            Node temp = table[hash];
            while(temp!=null) {
                if(temp.key.equals(key)) { //如果相等则说明找对了键值对,返回相应的value
                    value = (V) temp.value;
                    break;
                }else {
                    temp = temp.next;
                }
            }
        }
        return value;
    }
    
    public static int myHash(int v,int length) {
//        System.out.println("hash in myHash:"+(v&(length-1))); //直接位运算,效率高
//        System.out.println("hash in myHash:"+(v%(length-1))); //取模运算,效率低
        return v&(length-1);
    }
}

结果如下:

key重复了
{20:ss,53:ee,69:ff,85:gg,10:aa,30:cc}
ee

HashMap底层实现原理

HashMap底层实现原理

HashMap底层实现采用了哈希表,这是一种非常重要的数据结构,对于理解很多技术都非常有帮助(比如:redis数据库的核心技术和HashMap一样)。

数据结构中由数组和链表来实现对数据的存储,他们各有不同的特点。

(1) 数组:占用空间连续。 寻址容易,查询速度快。但增加和删除效率非常低。

(2) 链表:占用空间不连续。 寻址困难,查询速度慢。但增加和删除效率非常高。

哈希表的本质就是“数组+链表”,结合了数组和链表的优点(即查询快,增删效率高)。

HashMap基本结构

HashMap源码有如下两个核心内容:

public class HashMap<K,V> extends AbstractMap<K,V> 
   implements Map<K,V>, Cloneable, Serializable {
    /**
     * The default initial capacity - MUST be a power of two.
     * 核心数组默认初始化的大小为16(数组大小必须为2的整数幂)。
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    
    /**
     * The load factor used when none specified in constructor.、
     * 负载因子(核心数组被占用超过0.75则自动开始扩容)。
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     * 核心数组(根据需要可以扩容)。数组长度必须始终为2的整数幂。
     */
    transient Node<K,V>[] table;
    //以下代码省略
}

其中的Node<K,V>[] table 就是HashMap的核心数组结构,也称之为“位桶数组”。Node<K,V>源码如下:

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<K,V>对象存储了:

(1)key:键对象 value:值对象

(2)next:下一个节点

(3)hash:键对象的hash值

每一个Node<K,V>对象都是一个单向链表结构,如下表示一个Node<K,V>对象的典型示意:

Node对象存储结构

如下为Node<K,V>[]数组的结构(也是HashMap的结构):

Node数组存储结构

存储数据过程put(key,value)

HashMap存储数据的核心是如何产生hash值,该值用来对应数组的存储位置。

HashMap存储数据过程

我们的目的是将”key-value两个对象”成对存放到HashMap的Node<K,V>[]数组中。步骤如下:

(1) 获得key对象的hashcode

​ 首先调用key对象的hashcode()方法,获得hashcode。

(2) 根据hashcode计算出hash值(要求在[0, 数组长度-1]区间)

​ hashcode是一个整数,需要将它转化成[0, 数组长度-1]的范围。我们要求转化后的hash值尽量均匀地分布在[0,数组长度-1]这个区间,减少“hash冲突”。

​ i. 一种极端简单和低下的算法是:

​ hash值 = hashcode/hashcode;

​ 也就是说hash值总是1。意味着键值对对象都会存储到数组索引1位置,这样就形成一个非常长的链表。相当于每存储一个对象都会发生“hash冲突”,HashMap也退化成了一个“链表”。

​ ii. 一种简单和常用的算法是(相除取余算法):

​ hash值 = hashcode%数组长度

这种算法可以让hash值均匀地分布在[0,数组长度-1]的区间。 早期的HashTable就是采用这种算法。但是,这种算法由于使用了“除法”,效率低下。JDK后来改进了算法。首先约定数组长度必须为2的整数幂,这样采用位运算即可实现取余的效果:hash值 = hashcode&(数组长度-1)。

​ iii. 如下为自己测试简单的hash算法:

public class Test {
    public static void main(String[] args) {
        int h = 25760399;
        int length = 16;//length为2的整数次幂,则h&(length-1)相当于对length取模
        System.out.println(myHash(h, length));
    }
    /**
     * @param h  任意整数
     * @param length 长度必须为2的整数幂
     * @return
     */
    public static int myHash(int h,int length){
        System.out.println(h&(length-1));
        //length为2的整数幂情况下,和取余的值一样
        System.out.println(h%length);//取余数
        return h&(length-1);
    }
}

运行如上程序,就能发现直接取余(h%length)和位运算(h&(length-1))结果是一致的。事实上,为了获得更好的散列效果,JDK对hashcode进行了两次散列处理(核心目标就是为了分布更散更均匀)。

(3) 生成Node<K,V>对象

​ 如上所述,一个Node<K,V>对象包含4部分:key对象、value对象、hash值、指向下一个Node<K,V>对象的引用。我们现在算出了hash值。下一个Node<K,V>对象的引用为null。

(4) 将Node<K,V>对象放到table数组中

​ 如果本Node<K,V>对象对应的数组索引位置还没有放Node<K,V>对象,则直接将Node<K,V>对象存储进数组;如果对应索引位置已经有Node<K,V>对象,则将已有Node<K,V>对象的next指向本Node<K,V>对象,形成链表。

总结如上过程

当添加一个元素(key-value)时,首先计算key的hash值,以此确定插入数组中的位置,但可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,就形成了链表,同一个链表上的hash值是相同的,所以说数组存放的是链表。 JDK8中,当链表长度大于8时,链表就转换为红黑树,大大提高了查找的效率。

取数据过程get(key)

我们需要通过key对象获得“键值对”对象,进而返回value对象。步骤如下:

(1) 获得key的hashcode,通过hash()散列算法得到hash值,进而定位到数组的位置。

(2) 在链表上挨个比较key对象。 调用equals()方法,将key对象和链表上所有节点的key对象进行比较,直到碰到返回true的节点对象为止。

(3) 返回equals()为true的节点对象的value对象。

明白了存取数据的过程,下面再看下hashcode()和equals方法的关系:

Java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashcode。因为如果equals()为true而两个对象的hashcode不同,那在整个存储过程中就发生了悖论。

扩容问题

HashMap的位桶数组,初始大小为16。实际使用时大小是可变的。如果位桶数组中的元素达到(0.75*数组 length), 则重新调整数组大小变为原来2倍大小。

扩容很耗时,扩容的本质是定义新的更大的数组,并将旧数组内容挨个拷贝到新数组中。

JDK8将链表在大于8情况下变为红黑二叉树

JDK8中,HashMap在存储一个元素时,当对应链表长度大于8时,链表就转换为红黑树,这样又大大提高了查找的效率。

自定义一个LinkedList

自定义一个LinkedList

LinkedList特点和底层实现

LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。

双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。 所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。下图为LinkedList的存储结构图:

自定义一个链表1

每个节点都应该有3部分内容:

class  Node {
        Node  previous;     //前一个节点
        Object  element;    //本节点保存的数据
        Node  next;         //后一个节点
}

查看LinkedList的源码,可以看到里面包含了双向链表的相关代码:

自定义一个链表2

自定义一个LinkedList,体会底层原理

代码如下:

package com.msl.mycollection;
/**
 * 自定义实现一个链表
 * @author Senley
 */
public class MslLinkedList<E> {
    
    private Node first;
    private Node last;
    private int size;
    
    public static void main(String[] args) {
        MslLinkedList<String> list = new MslLinkedList();
        list.add("a");
        list.add("b");
        list.add("c");
        System.out.println(list);
        System.out.println(list.get(2));
        list.remove(0);
        System.out.println(list);
        list.add(1, "插入");
        System.out.println(list);
    }
    
    public void add(int index,E element) {
        checkRange(index);
        Node newNode = new Node(element);
        Node temp = getNode(index);
        if(temp!=null) {
            Node up = temp.previous;
            up.next = newNode;
            newNode.previous = up;
            newNode.next = temp;
            temp.previous = newNode;
        }
    }
    
    public void remove(int index) {
        checkRange(index);
        Node temp = getNode(index);
        if(temp!=null) {
            Node up = temp.previous;
            Node down = temp.next;
            if(up!=null) {
                up.next = down;
            }
            if(down!=null) {
                down.previous = up;
            }
            //被删除的元素是第一个元素时
            if(index==0) {
                first = down;
            }
            //被删除的元素是最后一个元素时
            if(index==size-1) {
                last = up;
            }
            size--;
        }
    }
    
    public void add(E element) {
        Node node = new Node(element);
        if(first==null) {
//            node.previous = null;
//            node.next = null;
            first = node;
            last = node;
        }else {
            node.previous = last;
            node.next = null;
            last.next = node;
            last = node;
        }
        size++;
    }
    
    public E get(int index) {
        checkRange(index);
        Node temp = getNode(index);
        return temp!=null?(E)temp.element:null;
    }
    
    private void checkRange(int index) {
        if(index<0 || index>size-1) {
            throw new RuntimeException("索引数字不合法:"+index);
        }
    }
    
    private Node getNode(int index) {
        checkRange(index);
        Node temp = null;
        if(index<=(size>>1)) {//size>>1相当于除以2
            temp = first;
            for(int i=0;i<index;i++) {
                temp = temp.next;
            }
        }else {
            temp = last;
            for(int i=size-1;i>index;i--) {
                temp = temp.previous;
            }
        } 
        return temp;
    }
    
    @Override
    public String toString() {
        //[a b c] first = a, last = c
        StringBuilder sb = new StringBuilder("[");
        Node temp = first;
        while(temp!=null) {
            sb.append(temp.element+",");
            temp = temp.next;
        }
        sb.setCharAt(sb.length()-1, ']');
        return sb.toString();
    }
    
}

结果如下:

[a,b,c]
c
[b,c]
[b,插入,c]

自定义一个ArrayList

自定义一个ArrayList

ArrayList特点和底层实现

ArrayList底层是用数组实现的存储。 特点:查询效率高,增删效率低,线程不安全。查看源码:

自定义一个ArrayList1

可以看出ArrayList底层使用Object数组来存储元素数据。所有的方法,都围绕这个核心的Object数组来开展。

我们知道,数组长度是有限的,而ArrayList是可以存放任意数量的对象,长度不受限制,那么它是怎么实现的呢?本质上就是通过定义新的更大的数组,将旧数组中的内容拷贝到新数组,来实现扩容。 ArrayList的Object数组初始化长度为10,如果我们存储满了这个数组,需要存储第11个对象,就会定义新的长度更大的数组,并将原数组内容和新的元素一起加入到新数组中,源码如下:

自定义一个ArrayList2

自定义一个ArrayList,体会底层原理

代码如下:

package com.msl.mycollection;

import javax.management.RuntimeErrorException;

/**
 * 自定义实现一个ArrayList,体会底层原理
 * @author Senley
 *
 */
public class MslArrayList<E> {
    
    private Object[] elementData;
    private int size;
    private static final int DEFALT_CAPACITY = 10;
    
    public MslArrayList() {
        elementData = new Object[DEFALT_CAPACITY];
    }
    
    public MslArrayList(int capacity) {
        if(capacity<0) {
            throw new RuntimeException("容器的容量不能为负数");
        }else if(capacity==0) {
            elementData = new Object[DEFALT_CAPACITY];
        }else {
            elementData = new Object[capacity];
        }
    }
    
    public int size() {
        return size;
    }
    
    public boolean isEmpty() {
        return size==0?true:false;
    }
    
    public void add(E element) {
        //什么时候扩容
        if(size == elementData.length) {
            //扩容操作
            Object[] newArray = new Object[elementData.length + (elementData.length>>1)];//10-->10+10/2
            System.arraycopy(elementData, 0, newArray, 0, elementData.length);
            elementData = newArray;        
        }
        elementData[size++] = element;
    }
    
    public E get(int index) {
        checkRange(index);
        return (E) elementData[index];
    }
    
    public void set(E element,int index) {
        checkRange(index);
        elementData[index] = element;
    }
    
    public void checkRange(int index) {
        //索引合法判断[0,size)
        if(index<0 || index>size-1) {
            //不合法
            throw new RuntimeException("索引不合法:"+index);
        }
    }
    
    public void remove(E element) {
        //将element和所有元素挨个比较,获得第一个比较为true的,返回.
        for(int i=0;i<size;i++) {
            if(element.equals(get(i))) {//容器中所有的比较操作都是用的equals而不是==
                //将该元素从此处移除
                remove(i);
            }
        }
    }
    
    public void remove(int index) {
        int numMoved = elementData.length-index-1;
        if(numMoved>0) {
            System.arraycopy(elementData, index+1, elementData, index, elementData.length-index-1);
        }
        elementData[--size] = null;
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for(int i=0;i<size;i++) {
            sb.append(elementData[i]+",");
        }
        sb.setCharAt(sb.length()-1, ']');
        return sb.toString();
    }
    
    public static void main(String[] args) {
        MslArrayList m = new MslArrayList(20);
        for(int i=0;i<40;i++) {
            m.add("senley"+i);
        }
        System.out.println(m);
        m.set("test", 10);
        System.out.println(m.get(10));
        m.remove(3);
        System.out.println(m);
        m.remove("test");
        System.out.println(m);
        System.out.println(m.size);
        System.out.println(m.isEmpty());
    }
}

结果如下:

[senley0,senley1,senley2,senley3,senley4,senley5,senley6,senley7,senley8,senley9,senley10,senley11,senley12,senley13,senley14,senley15,senley16,senley17,senley18,senley19,senley20,senley21,senley22,senley23,senley24,senley25,senley26,senley27,senley28,senley29,senley30,senley31,senley32,senley33,senley34,senley35,senley36,senley37,senley38,senley39]
test
[senley0,senley1,senley2,senley4,senley5,senley6,senley7,senley8,senley9,test,senley11,senley12,senley13,senley14,senley15,senley16,senley17,senley18,senley19,senley20,senley21,senley22,senley23,senley24,senley25,senley26,senley27,senley28,senley29,senley30,senley31,senley32,senley33,senley34,senley35,senley36,senley37,senley38,senley39]
[senley0,senley1,senley2,senley4,senley5,senley6,senley7,senley8,senley9,senley11,senley12,senley13,senley14,senley15,senley16,senley17,senley18,senley19,senley20,senley21,senley22,senley23,senley24,senley25,senley26,senley27,senley28,senley29,senley30,senley31,senley32,senley33,senley34,senley35,senley36,senley37,senley38,senley39]
38
false
  • Copyrights © 2021-2022 Senley
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信