java基础-3-泛型与集合

1 泛型

  • 数据类型的参数化,帮助建立类型安全;确保数据的安全性和免去强制类型转换;它供javac即编译使用, 将运行时异常(如类型转换异常)提升到编译期去发现;编译完毕擦除泛型相关类型

    List list = new ArraryList();
    Object obj = list.iterator().next(); // 不使用泛型,集合中元素取出为Object类型
    String str = (String)obj; // 强制类型转换,可能ClassCastException
    ​
    List<String> list = new ArrayList<>(); // 使用泛型进行约束,如此集合元素添加时只能添加符合的;元素获取时类型也明确,更方便遍历
  • 自定义泛型类:可参考ArrayList类去编写

  • < T>:用于限制参数类型即确保参数类型一致;具体类型在运行期决定,常用于类或方法上;< Object>由于类型范围太大,在写操作时常需要向上转型,读操作时向下转型,所以不做首选;配合集合如List< T>,集合可进行读写操作即能调用add()等方法

  • < ?>:任意参数类型即在不关心具体数据类型时使用通配符占位,常用于变量上;< ? extends T>上界通配符即入参需要是T类型或其子类型,< ? super T>下界通配符即入参需要是T类型或其父类型;配合集合如List< ?>,编译器无法确定数据类型,即集合是只读,不能增改,但可以清删

2 集合 / 容器

  • 存储引用数据类型(基本数据类型装箱);可存储不同类型的数据

  • 重写了toString() ,打印时输出[..., ...]

2.1 Collection

  • 重写了equls()和hashCode(), 集合中元素用equals()比较,不用==严格判断

  • 增:

    • boolean add(object)注意当参数是另一个集合时,添加变成元素+1,即[本来的元素列表,[okject]]

    • boolean addAll(collection):注意当参数是另一个集合时,是将该集合中所有元素添加,元素+n,即[本来的元素列表,新加集合的元素列表]

  • 删:

    • void clear():集合中的所有元素的引用置为null;区别于集合=null,前者是集合中元素的引用对象被释放,后者是此集合被释放(当然如此一来元素也被释放)

    • boolean remove(object):移除

    • boolean removeAll(collection)

  • 查:

    • boolean contains(obj) :是否包含元素

    • boolean containsAll(collection):是否包含集合

    • boolean isEmpty():集合中是否有元素;clear()操作之后返回true

    • int size():集合大小

  • 改:

    • Object[] toArray():集合转为数组

    • boolean retainAll(collection) :保留,对当前集合只保留与指定集合中所有相同的元素;如果两个集合所有元素相同,返回flase

2.1.1 List

2.1.1.1 公有

  • 有序:元素的取出时顺序与元素存入时的顺序一致;因为有序,对比Collection接口,方法多了索引参数index

  • 元素可重复;允许多个null元素

  • 增:

    • void ensureCapacity(int minCapacity):增加集合容量

    • void add(index, obj) / boolean addAll(index, collection)

  • 删:

    • Object remove(index):返回被删的元素;失败会抛异常

  • 查:

    • Object get(index):获取指定索引的元素

    • int indexOf / lastIndexOf(obj):获取元素的索引 / 逆向索引

    • ListIterator listIterator():获取所有元素;list集合特有的迭代器,同Iterator,用于集合的元素的增删查改

  • 改:

    • void trimToSize():调整长度为元素个数,优化存储空间

    • Object set(index, element):指定索引处元素

    • List subList(fromIndex, toIndex):截取指定范围内元素为新集合

2.1.1.2 ArrayList

  • 基于索引的数据结构,底层为动态数组,替代Vector

  • 查询快:数组是连续存储单元,堆中存储实例化元素的地址和值,遍历地址所以速度快;下标遍历和随机访问如get(index)的时间复杂度O(1)

  • 增删慢:需要重新计算大小和更新索引;不是尾插法时需要移动元素;超出容量做扩容时还要新建数组并复制数据;建议尾插法和指定初始容量,以防止动态扩充次数太多而影响性能;初始容量10,扩容为1.5倍

  • 自定义实现ArrayList:

     /**
      * 数组:连续存储单元,引用类型,堆中存储实例化元素的地址和值,遍历地址所以遍历快;固定容量
      * int[] array = new int[10]/int[]{1,2,3};
      * 动态数组ArrayList:
      */
     public class MyArrayList<E> {
        private int size = 0; // 标记可写位置,每插入一个元素,size++;标记可读位置size-1
        private static final int CAPACITY = 10;
        // public E[] elements; //java.lang.ArrayStoreException:将错误类型的对象存储到一个对象数组
        public Object[] elements;
     ​
        public MyArrayList() {
            this(CAPACITY);
        }
     ​
        public MyArrayList(int capacity) {
            // this.elements = new E[capacity];这写法报错
            this.elements = new Object[capacity];
        }
     ​
        public void add(E element) {
            // this.elements[this.size++] = element;
            // 可以复用,即默认的添加是在最后一个元素之后,size = 0
            this.add(this.size, element);
        }
        public void add(int index, E element) {
            if (index < 0 || this.size > this.elements.length) {
                throw new IndexOutOfBoundsException();
            }
            this.addCapacity();
            // 元素从后开始遍历,元素后移,腾出位置
            for (int i = this.size; i > index; i--) {
                this.elements[i] = this.elements[i - 1];
            }
            this.elements[index] = element;
            size++;
        }
     ​
        private void addCapacity() {
            if (this.size < this.elements.length) {
                return;
            }
            int newCapacity = this.elements.length + (this.elements.length >> 1);
            Object[] newElements = new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                newElements[i] = this.elements[i];
            }
            this.elements = newElements;
        }
     ​
        public Object remove(int index) {
            check(index);
            Object removeElement = this.elements[index];
            for (int i = index; i < this.size; i++) {
                this.elements[i] = this.elements[i + 1];
            }
            size--;
            return removeElement;
        }
        public void set(int index, E element) {
            check(index);
            this.elements[index] = element;
        }
        public Object get(int index) {
            check(index);
            return this.elements[index];
        }
        private void check(int index) {
            if (index < 0 || index >= this.size) {
                throw new IndexOutOfBoundsException();
            }
        }
     ​
        /**
         * 元素是引用类型,把size设置为0让其不可用仅适用于基本类型
         * 因为对象存储在堆上,清除时将地址置为null即释放地址指向的引用,才能被gc
         */
        public void clean() {
           for (int i = 0; i < this.size; i++) {
               elements[i] = null;
           }
           this.size = 0;
        }
     ​
        @Override
        public String toString() {
            return "MyArrayList{" +
                    "elements=" + Arrays.toString(elements) +
                    '}';
        }
     }

2.1.1.3 LinkedList

  • 以元素列表形式存储/线性存储 数据,底层为(双向)链表:散列存储,对内存连续性要求不高,但每个节点与前后元素链接,节点多存储了指向前后元素的引用,所以更占内存

  • 查询慢,即O(n):需要移动指针;遍历时用迭代器,不能用for(每一次get(i)都会遍历list)

  • 增删快,O(1)

  • LinkedList针对首尾元素而专有的方法:

    • addFirst();addLast();添加元素,链表满时抛出异常

    • offerFirst();offerLast():(JDK1.6)添加元素,链表满时返回false

    • removeFirst();removeLast():删除元素;NoSuchElementException

    • poll();pollFirst();pollLast():获取元素后删除,(JDK1.6)如果链表为空,返回null

    • getFirst();getLast():获取首尾元素,如果链表为空,抛出NoSuchElementException

    • peekFirst();peekLast():获取首尾元素,(JDK1.6)如果链表为空,返回null

2.1.1.4 Vector

  • 动态数组,线程同步,并发性低 ,古老,查询和增删都慢;初始容量10,扩容一倍

2.1.1.5 代码

// ArrayList测试
private static void testArrayList() {
	List<String> arrayList = new ArrayList<>();
    arrayList.add("a");
    arrayList.add("b");
    arrayList.add("c");
    // arrayList.add(3);
    int size = arrayList.size();
    for (int i = 0; i < size; i++) {
        System.out.println(arrayList.get(i));
    }
    System.out.println(arrayList.contains("a")); // true
    arrayList.remove("c"); // removeAll()
    String[] array = arrayList.toArray(new String[]{});
}
// LinkedList测试
private static void testLinkedList(){
}
// Vector测试
private static void testVector(){
}
// HashSet测试
private static void testHashSet() {
    Set<String> set = new HashSet<String>();
    Set<String> set1 = new HashSet<String>();
    Set set2 = new HashSet();
    set.add("a");
    set1.add("b");
    set.addAll(set1);
    set2.addAll(set);
    Integer integer = 1;
    set2.add(integer);
    String[] strings = set.toArray(new String[]{});
    for (String string:strings) {
        System.out.println(string);
    }
    for (String str:set) {
        System.out.println(str);
    }
    System.out.println(set2.size());
    System.out.println(set2);
    for(Object object: set2){
        if (object instanceof Integer) {
            int i = ((Integer) object).intValue();
            System.out.println(i);
        } else if (object instanceof String) {
            System.out.println(object);
        }
    }
}

2.1.2 Set

2.1.2.1 公有

  • 无序,遍历只能用迭代器

  • 元素唯一:唯一性通过元素的hashCode()和equals()保证,add()时先调用这两个方法去判断;先hashCode()得到元素的hash值,等同于Map中的key,表示元素存放的地址,集合中不存在此元素的hash值,则直接添加;否则地址相同后哈希冲突,会再用equals()比较内容,为true表示元素相同,因此value值覆盖原来的元素(key不变);最多有一个null

  • Set存储自定义类,为了保证存储的对象唯一(如User中,同age,sex,name时认为是同一个对象,只存储一次),则需要重写hashCode()和equals();未重写前new对象后hash值明显不一致

    @override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || this.getClass() != o.getClass()) return false;
        User user = (User)o;
        return this.age == user.age && this.name.equals(user.name);
    }
    @override
    public int hashCode() {
        return Object.hash(age, name);
    }
  • 增删,查找性能都高

  • 哈希/散列表的原理:

    • 对对象元素中的关键字(对象中的特有数据如fileld,为了提高效率,最好保证对象的关键字唯一),进行哈希算法运算,并得出具体的算法值(hash值,哈希码,散列码,int型整数)

    • hash值表示这个元素的位置(所以快速定位),是Object模拟的内存地址,通过hashCode()获取,可重写此方法返回值;存储hash值的结构即为hash表(元素用单向链表实现的散列表);这类似数组的索引,但hash值不是连续的

    • 如果hash值出现冲突即相同,则需要再equals(),即判断这个关键字对应的对象是否相同。不同则重新散列(如key顺延+1)到其他位置

    • 如此减少了equals()次数

    • 能判断equals()且为true了,则hashCode()一定相同;如果equals()被覆盖,则hashCode()也必须覆盖

  • 方法基本同父接口Collection

2.1.2.2 HashSet

  • 底层基于HashMap实现;所以Map和Set通过迭代器可以相互转换

  • set.add(元素),该元素对应map的key,因此都是不重复的,value为每次new Object()

  • 构造器中实例化的HashMap的加载因子都是0.75,容量16

2.1.2.3 LinkedHashSet

  • HashSet + 链表实现;链表用来维护元素的顺序;但元素依旧不能重复

2.1.2.4 TreeSet

  • 底层为TreeMap

2.1.2.5 EnumSet

2.1.3 Queue

  • 新集合;队列,FIFO即remove()方法移除最先add()的元素;队尾进/插入,队首出/删除;常用在生产者消费者模式

  • 阻塞队列BlockingQueue:队列中为空时,获取元素操作将会阻塞;队列中元素已满时,插入操作将会阻塞;阻塞队列帮助控制何时阻塞线程,何时唤醒线程,甚至兼顾效率和线程安全

  • add()插入,remove()移除,element()检查:抛出IllegalStateException:Queue full或NoSuchElementException

  • 特殊值:它们的方法不会报异常,而是返回插入成功与否的true和false,移除的元素或null

    • offer()插入,poll()移除,peek()检查;有重载的参数为时间的方法即超时阻塞,时间规定队列会阻塞线程一定时间,超时后线程退出

    • put()插入,take()移除:一直阻塞,即队列满时再调用插入,线程会一直等待队列中移除一个元素,然后插入;队列空时再调用移除,则线程一直等待队列插入一个能供移除的元素

2.1.3.1 ArrayBlockingQueue

  • 基于数组结构的有界阻塞队列;默认有界为10

2.1.3.2 LinkedBlockingQueue

  • 基于链表结构的阻塞队列,吞吐量高

2.1.3.3 SynchronousQueue

  • 不存储元素的阻塞队列,即单个元素队列;插入操作必须等待其他线程将元素移除,否则一直处于阻塞状态

    // 传统模式
    class Data {
        private int number = 0;
        private Lock lock = new ReentrantLock();
        private Condition condition = lock.newCondition();
        
        public void increment() throw Exception {
            lock.lock();
            try {
                // 等待,不生产
            	while (0 != number) {
                	condition.await();
            	}
                // 生产
            	number++;
                condition.singalAll(); // 通知唤醒
            } finally {
                lock.unlock();
            }
        }
        
        public void decrement() throw Exception {
            lock.lock();
            try {
                // 等待,不消费
            	while (0 == number) {
                	condition.await();
            	}
                // 消费
            	number--;
                condition.singalAll(); // 通知唤醒
            } finally {
                lock.unlock();
            }
        }
    }
    main(String args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.increment();
            }
        }, "增线程").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                data.decrement();
            }
        }, "减线程").start();
    }
    
    // 阻塞队列模式:等待和唤醒无需关注
    class Data {
        private volatile boolean FLAG = true; // 默认进行生产和消费;设置可见,让生产和消费同时进行
        private AtomicInteger atomicInteger = new AtomicInteger();
        BlockingQueue<String> blockingQueue = null;
        public Data(BlockingQueue<String> blockingQueue) {
            this.blockingQueue = blockingQueue;
        }
        
        public void increment() throw Exception {
            String data = null;
            while (FLAG) {
                // 2S内添加数据
                data = atomicInteger.incrementAndGet() + "";
                returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
            }
        }
        
        public void decrement() throw Exception {
            String result = null;
          	while (FLAG) {
                result = blockingQueue.poll(2L, TimeUnit.SECONDS);
                // 2S内没有获取到数据
                if (StringUtils.isEmpty(result)) {
                    FLAG = false;
                    return;
                }
            }
        }
    }
    main(String args) {
        Data data = new Data(new ArrayBlockingQueue<>(10));
        new Thread(() -> {
            data.increment();
        }, "增线程").start();
        new Thread(() -> {
            data.decrement();
        }, "减线程").start();
    }


2.2 Map

2.2.1 公有

  • key-value

  • 增:

    • Object put(key, value)

      • 添加时一般直接调用方法,不写返回值;返回值由key决定,key不存在,返回null,否则返回key对应的value,添加后新的value替换此value

      • 当key为引用数据类型,且该引用对象没有重写equals(),则因其hashCode()不同,所以是两个key;如key都为new Person(1, "姓名"),打印map时可看到相同key的两个key-value对,但注意实际是不同的;区别于将其定义为一个变量person,做两次put(),实际后一次会覆盖前一次,毕竟此时key是同一个

    • void putAll(map):增加map

  • 删:

    • void clear():清除元素引用

    • Object remove(key):移除key指定的元素;key存在,返回value,key不存在,返回null;一般直接调用,不写返回值,如果写了返回值,注意value类型是基本数据类型时,用包装类接收,否则报空指针

  • 查:

    • Object get(key):键不存在,或者键有而value为null,则返回null;不能用此判断是否包含key,即以此获取不存在的key时返回null而不是抛出异常,再用null去业务处理如toString()时则空指针

    • boolean isEmpty():判断map是否为null

    • boolean containsKey(key):是否包含key

    • boolean containsValue(value):是否包含value

    • Collection values():将所有value作为Collection

  • Map中key和value的遍历以及与Set的互相转换:

     // 获取Map中所有元素
     Set keySet = map.keySet(); // 将map的所有key作为set
     for (Integer key: keySet) // 简单遍历,假设key为Integer类型
     Iterator it = keySet.iterator(); // 迭代器遍历,返回set的迭代对象
     // 如果确定集合中元素类型且类型统一,则Iterator<>较好,如Iterator<String>
     // 方法2:已知元素类型如String,则直接foreach遍历
     for(String string: keySet) {}
     // 当存在多种类型时
     for(Object obj: keySet) { 
         if(obj instanceof …) {…} // 注意判断基本数据类型时是instanceof对应的包装类
         else if(obj instanceof …) {…}
     }
     // 方法3:转为数组去遍历
     String[] strings = keySet.toArray(new String[]{});
     ​
     // 遍历迭代对象:while方式
     while(it.hasNext()) {
         Object key = it.next();
         Object value = map.get(key);
     }
     // for方式
     for (Iterater<Object> keySet: it) {}
     ​
     Set entrySet = map.entrySet(); // 将key-value对作为set
     Iterator it = entrySet.iterator();
     while(it.hasNext()) {
         Map.Entry  me = (Map.Entry)it.next(); // Entry是Map内部类,即元素数组(key-value)是它的对象,持有指向下一个元素的引用
         System.out.println(me.getKey()+"::::"+me.getValue());
     }
     ​
     // ----------------------------------------------------------
     // 遍历键值对
     Set<Map.Entry<Integer,String>> entrySet = map.entrySet();
     for (Map.Entry entry: entrySet) {
         System.out.println(entry.getKey() + "->" + entry.getValue());
     }
     // 遍历键,通过键得到值
     Set<Integer> keys = map.keySet();
     for (Integer integer: keys) {
         String value = map.get(integer);
         System.out.println(integer + "=>" + value);
     }
     // 遍历值
     Collection<String> values= map.values();
     for (String value:values) {
         System.out.println(value);
     }

2.2.2 Hashtable

  • 继承自Dictinary类;实现Map,Cloneable(可复制),Serializable三个接口

  • key不支持null

  • 线程安全/同步,它的每个操作数据的方法,如remove(),get(),put()等都被做了同步控制

  • 初始容量11;扩容2n + 1

  • 不保证元素插入的次序:如map.put(1, "1");map.put(2, "2");不一定先插入1;但输出的时候一定和插入的时候顺序一致,如先插入的如果是1,则先输出的也是1

  • 特有方法:elements();contains()

  • 不推荐;保留的古老类,性能低;同步机制虽然避免了多线程同时操作,但在iterator遍历中的put,remove,clear等操作却会被多线程执行成功

2.2.3 HashMap

  • 替代Hashtable;数组 + 链表;查询效率O(N)

  • JDK1.8优化:数组+链表+红黑树,即链表节点数预设值8:当链表节点数超过8,数组长度超过64时,链表转为红黑树,元素以内部类Node节点存储;否则链表深度太深,增删查找性能下降;节点数低于6时又转回链表;查询效率O(logn)

  • 继承自AbstractMap类;实现Map,Cloneable,Serializable三个接口

  • key可以有一个为null,存在下标为0处

  • 初始容量16;扩容2n;扩容时机为:超过容量乘以负载/散列因子(默认为0.75)时;加载因子表示hash表中元素填满的程度,太小则浪费空间,太大则增加了元素冲突几率,都会导致查询性能下降

    • 容量设置为16即2的次幂:在取模/触发运算时比较耗时,此时可转为位运算,这也是HashMap比Hashtable效率高的原因之一

  • hashCode()计算key的hash值,值与map大小取余,得到元素在HashMap(hash表)中的存储(索引)位置;值已存在即该位置已经存在元素,则增加了链表结构;遍历链表中数据执行equals(),相同则覆盖(区别于HashSet,相同时插入操作不执行),都不同则该元素插入链表;优化则尽量让hash值分布均匀而不出现链表,省去遍历链表

  • 自定义实现HashMap:

     /**
      * 自定义HashMap:封装类成员:
      * key:
      * value:
      * next:链表指向下一节点,key重复时next不为null;双向链表则有prev
      * hash:hash值,key重复时,get操作还需要对比hash值
      * 实现get()/put()
      */
     public class MyMap<K, V> {
        // 数组数据结构,数据类型为定义的链表
        private Entry<K, V>[] keyTable = null;
        // 容量
        private int size = 0;
        // 默认容量
        private static int defaultLength = 16;
     ​
        public MyMap() {
            this.keyTable = new Entry[defaultLength];
        }
     ​
        // 实现map.put();注意返回类型为V,hashMap源码的put()也是返回的V
        public V put(K k, V v) {
            int index = hash(k);
            Entry<K, V> kvEntry = this.keyTable[index];
            if (kvEntry == null) {
                this.keyTable[index] = new Entry<>(k, v, index, null);
                size++;
            } else {
                this.keyTable[index] = new Entry<>(k, v, index, kvEntry);
            }
            return this.keyTable[index].getV();
        }
     ​
        // 通过hash计算key的位置:0 - 15
        private int hash(K k) {
            int i = k.hashCode() % (defaultLength - 1);
            return Math.abs(i);
        }
     ​
        // 实现map.get()
        public V get(K k) {
            if (size == 0) {
                return null;
            }
            int index = hash(k);
            Entry<K, V> entry = getEntry(k, index);
            return entry == null ? null : entry.getV();
        }
     ​
        private Entry<K, V> getEntry(K k, int index) {
            for (Entry<K, V> entry = this.keyTable[index]; entry != null ; entry = entry.next) {
                if (entry.hash == index && (k == entry.getK() || k.equals(entry.getK()))) {
                    return entry;
                }
            }
            return null;
        }
     ​
        // 链表数据结构
        class Entry<K, V> {
            K k;
            V v;
            int hash;
            Entry<K, V> next;
     ​
            public Entry(K k, V v, int hash, Entry<K, V> next) {
                this.k = k;
                this.v = v;
                this.hash = hash;
                this.next = next;
            }
            
            public K getK() {
                return k;
            }
            
            public void setK(K k) {
                this.k = k;
            }
            
            public V getV() {
                return v;
            }
            
            public void setV(V v) {
                this.v = v;
            }
        }
     }

2.2.4 ConcurrentHashMap

  • 综合Hashtable和HashMap;底层基于数组+链表+红黑树;线程安全,通过CAS实现的乐观锁保证

  • 二义性:get()方法返回null时,不清楚是因为key不存在而返回null,还是因为key存在但value值为null而返回null;HashMap用containsKey()方法避免了这个问题,但ConcurrentHashMap没有解决二义性问题,因为它常用在并发环境下,get()与containsKey()方法调用期间无法确保其他线程的操作使得元素被设置或删除了,所以使用ConcurrentHashMap则key和value都不能为null

2.2.5 LinkedHashMap

  • 使用双向链表来维护元素次序;会记录元素插入/取出的顺序

2.2.6 TreeMap

  • 红黑二叉树实现;key需要实现Comparable接口,输出顺序基于其重写的compareTo()

2.2.7 代码

Map<Integer,String> map = new HashMap();
map.put(0, String.valueOf(0));
map.put(1, "1");
System.out.println("size:" + map.size());
System.out.println(map.get(0));

// 遍历键值对
Set<Map.Entry<Integer,String>> entrySet = map.entrySet();
for (Map.Entry entry: entrySet) {
    System.out.println(entry.getKey() + "->" + entry.getValue());
}
// 遍历键,通过键得到值
Set<Integer> keys = map.keySet();
for (Integer integer: keys) {
    String value=map.get(integer);
  System.out.println(integer + "=>" + value);
}
// 遍历值
Collection<String> values = map.values();
for (String value: values){
    System.out.println(value);
}
System.out.println(map+"----------------"+map.size());
// HashMap和Hashtable可以互转;内容全部清空
map = new Hashtable<>();
map.put(1, "new 1");
System.out.println(map + "--------------" + map.size());
HashMap linkedHashMap = new LinkedHashMap(); // 子类使用双重链表维护元素顺序
// Collections工具类测试
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("ab");
list.add("ac");
Collections.reverse(list); // 反序
System.out.println(list);
Collections.shuffle(list); // 随机排序
System.out.println(list);
Collections.sort(list); // 排序
System.out.println(list);
Collections.swap(list, 0, 1); // 交换
System.out.println(list);
Collections.rotate(list, 2); // 旋转,参数2表示移动个数
System.out.println(list);
System.out.println(Collections.min(list));
List synchronizedList = Collections.synchronizedList(list); // 使其线程安全

2.3 List与Map

  • List与Map合作:存储表格数据:

    • 表中每行存储为map:map1.put("id", id);map1.put("name", name);map2...

    • 整个表格存储为list;即List< Map< String, Object>>

    • 如果使用ORM思想:每一行为一个Bean,整个表存储为map或list

  • 集合的遍历:

    • 集合遍历时不允许修改集合,否则ConcurrentModificationException

    • 可以使用迭代器删除元素

      • 单线程:Iterator的remove();或者建立一个记录需要删除的元素的集合,循环完以后统一删除

      • 多线程:使用并发集合类


3 Collections

  • 集合工具类

  • 增:

    • void fill(list, obj):填充集合元素

  • 查:

    • Object min(collection)/max(collection):获取集合中最大、最小元素;前提是集合元素已实现可比较

    • int binarySearch(list, key):查找

  • 改:

    • void sort(list, [comparator]):元素的自然顺序排序/按指定规则排序

    • void swap(list, i, j):交换集合中两个位置上的元素

    • void reverse(list):元素反序

    • void shuffle(list):元素随机排序;洗牌思想:总最后一张开始向前遍历,与随机一张牌交换顺序

    • synchronizedXxx():包装集合使其线程安全;Xxx表示各种集合,参数为集合对象

      • 如ArrayList不是线程安全的,为了保证线程安全,手动用synchronized来同步所有调用其方法的地方;而工具类的synchronizedList()方法,底层实质也是在集合的每个方法上进行了synchronized同步且同一个监听器(Vector虽然线程安全,但效率低且不是同一个监听器,不能保证多个方法的组合是线程安全的)

      • 如果是读多写少的场景,即高并发情况下,注重效率又保证安全,则使用JUC,即JDK提供了java.util.concurrent并发集合包;Copy On Write写时复制思想:拷贝原始数据,进行操作;如CopyOnWriteArrayList,ConrurrentHashMap,CopyOnWriteArraySet等;非此场景无需使用,复制的代价较大

        // 源码
        public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock(); // 加锁
            try {
                Object[] elements = getArray(); // 集合元素转为数组
                int len = elements.length;
                Object[] newElements = Arrays.cppyOf(elements, len + 1); // 写时,复制:复制数组为新数组
                newElements[len] = e; // 元素添加到新数组末尾
                setArray(newElements); // 数组转为集合
                return true
            } finally {
                lock.unlock(); // 释放锁
            }
        }

    • emptyXxx() / singletonXxx() / unmodifiableXxx():设置集合不可变,只读

    • rotate(list, distance):元素旋转,参数2表示移动个数


4 Iterator

  • 迭代器:集合遍历

  • Iterator it = collection.iterator():接口,线程安全;容器都有迭代器对象;或foreach循环;List集合还可以根据索引使用普通for循环

  • List额外提供的方法:ListIterator listIt = list.listIterator();ListIterator(继承自Iterator)扩展的方法(向前迭代功能和添加元素):

    • boolean hasPrevious()

    • Object previous()

    • void add()

    • boolean hasNext()

    • Object next()

    • void remove()

  • 多线程对同一集合操作时,使用快速失败(fail-fast)机制:遍历过程中只能以此方法操作;不能使用集合或其他线程的操作,否则ConcurrentModificationException

  • 最佳实践:

    • 不建议同一个集合中存储多种类型的数据,即最好使用泛型来限定类如List< String>

    • 使用有限并发的集合类(易扩展)而非同步(线程安全)的

    • 使用接口来代表和访问集合(如List存储ArrayList)

    • 使用迭代器来循环集合

      // Iterator迭代器测试
      private static void testIterator(Collection<String> collection) {
          // foreach遍历
          for (String string:collection) {
              System.out.println(string);
         	} 
          // Iterator遍历
          Iterator<String> iterator = collection.iterator();
          while (iterator.hasNext()) {
              String string = iterator.next();
              System.out.println(string);
          }
          collection = new ArrayList<String>();
          collection.add("A");
          collection.add("B");
          collection.add("C");
      }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值