数组与Collectoin集合 List / Set

数组

java 数组需要先初始化才能使用,初始化后长度不可变

初始化后未填充的位置自动填充null

初始化方式:静态初始化(直接赋值)、动态初始化(先设定长度,再赋值)

 

Object数组能存储任意类型数据,包括基本数据类型

数组下标一般是直接用int类型,但是也可以用char类型,它会转换为ASCALL码表中对应的数值,char转int属性自动转换
 
查看元素 Arrays.toString 以字符串形式打印
 
加入元素:创建新数组,长度=arry.length+1
 
排序
  Arrays.sort();数组排序升序
  Arrays.sort(a,Collections.reverseOrder());降序

 

System.arrayCopy  数组复制
1 public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
2 代码解释:
3 Object src : 原数组
4 int srcPos : 从元数据的起始位置开始    // 序号从0开始,与下标一致
5 Object dest : 目标数组
6 int destPos : 目标数组的开始起始位置
7 int length  : 要copy的数组的长度

 

 

Object数组

1     @Test
2     public void objectArrTest() {
3         Object[] arr = new Object[3];
4         arr[0] = 1;
5         arr[2] = "2";
6         // [1, null, 2]
7         System.out.println(Arrays.toString(arr));
8     }

 

两种初始化方式:

 

 1     /**
 2      *  java 数组需要先初始化才能使用
 3      *  初始化方式:静态初始化(直接赋值)、动态初始化(先设定长度,再赋值)
 4      */
 5     @Test
 6     public void strArrayTest() {
 7         String a = "xxx";
 8         String b = "yyy";
 9         // 静态初始化:同时声明、初始化、赋值
10         String[] strArr = new String[]{a,b};
11         // [xxx, yyy]
12         System.out.println(Arrays.toString(strArr));
13 
14 
15         // 动态初始化:先声明、设定长度,后续再赋值
16         String[] strArr2 = new String[2];
17         strArr2[1] = "x";
18         // 数组声明写法2:[]和变量名可以换位置 不推荐
19         String strArr3[] = new String[2];
20         strArr3[0] = "y";
21         // [null, x] 未赋值,填充null
22         System.out.println(Arrays.toString(strArr2));
23         // [y, null]
24         System.out.println(Arrays.toString(strArr3));
25     }

 

 集合

基本介绍

 

 

 

 

 

  1. 集合主要是Collection和Map)两个接口
  2. Connection称为单列集合,属于单值操作,即每次只能操作一个对象
  3. Map称为双列集合,每次操作一组对象(k,v)  键值对

 

Collection

  • Collection 表示一组对象,
  • 这些对象也称为 collection 的元素。
  • 一些 collection 允许有重复的元素,而另一些则不允许。
  • 一些 collection 是有序的,而另一些则是无序的。
  • JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set 和 List)实现。
  • 此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
  • Set 无序,不重复
  • List 有序,可重复
  • AbstractCollection重写了toString()方法,可以直接打印元素

 AbstractCollection提供方法概括

 

 

 

 List接口

 

 

 实现类有ArrayList, LinkedList,Vector常用

常用API

添加元素:add

 

 获取元素:  get

 

 删除元素;remove

 

 是否包含:contains

 

 是否为空:isEmpty

 

 判断元素多少:size

 

 ArrayList

ArrayList的底层数据结构是数组 =》数组的动态实现

查询快,增删慢,线程不安全

默认扩充为原来的1.5倍

随着向 ArrayList 中不断添加元素,其容量也自动增长 =》动态扩容

此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出并发修改异常 ConcurrentModificationException
那Java本身就有数组了,为什么要⽤ArrayList呢?

(Arraylist动态扩容机制)

原⽣的数组会有⼀个特点:你在使⽤的时候必须要为它创建⼤⼩,⽽ArrayList不⽤。
在⽇常开发的时候,往往我们是不知道数组的⼤⼩的
如果数组的⼤⼩指定多了,内存浪费;如果数组⼤⼩指定少了,装不下
假设我们给定数组的⼤⼩是10,要往这个数组⾥边填充元素,我们只能添加10个元素。
⽽ArrayList不⼀样,ArrayList我们在使⽤的时候可以往⾥边添加20个,30个,甚⾄更多的元素
因为ArrayList是实现了动态扩容的
当我们new ArrayList()的时候,默认会有⼀个空的Object数组,⼤⼩为0
当我们第⼀次add添加数据的时候,会给这个数组初始化⼀个⼤⼩,这个⼤⼩默认值为10
使⽤ArrayList在每⼀次add的时候,它都会先去计算这个数组够不够空间
如果空间是够的,那直接追加上去就好了。如果不够,那就得扩容
                                                                --java3y
怎么扩容?⼀次扩多少?

扩容的原理是数组拷贝

在源码⾥边,有个grow⽅法,每⼀次扩原来的1.5倍。⽐如说,初始化的值是10嘛。
现在我第11个元素要进来了,发现这个数组的空间不够了,所以会扩到15
空间扩完容之后,会调⽤arraycopy来对数组进⾏拷⻉
 
                                                                 --java3y
 
    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

 

 
 
为什么在⽇常开发中⽤得最多的是ArrayList呢?
是由底层的数据结构来决定的,在⽇常开发中,遍历的需求⽐增删要多,即便是增删也是往往在List的尾
部添加就OK了。
像在尾部添加元素,ArrayList的时间复杂度也就O(1)
另外的是,ArrayList的增删底层调⽤的copyOf()被优化过
现代CPU对内存可以块操作ArrayList的增删⼀点⼉也不会⽐LinkedList慢
                                                                  --java3y
遍历方式
方式一:for循环遍历
1         for (int i = 0; i < arr.size(); i++) {
2                     System.out.println(arr.get(i));
3                 }
方式二:增强型for循环
1  for (Object object : array) {
2       System.out.println("object:"+object);
3     }
方式三:List专用迭代器

可以双向遍历

 

 List<String> aList = new ArrayList<>();
        aList.add("a");
        aList.add("b");
        aList.add("c");
        ListIterator<String> listIterator = aList.listIterator();
    // next后序遍历,起始位置,游标从-1算起
while (listIterator.hasNext()) { // 如果在末端,返回的是数组长度 也就是3 System.out.println("下一个位置:" + listIterator.nextIndex()); // 移动游标位置 System.out.println("下一元素" + listIterator.next()); } // 此时游标在末端, previous前序遍历,末尾位置 游标从数组长度算起 while(listIterator.hasPrevious()) { // 如果在末端,nextIndex()返回的是数组长度 也就是3 所以上一个位置是2 System.out.println("上一个位置:" + listIterator.previousIndex()); // 移动游标位置 System.out.println("上一元素" + listIterator.previous()); }

 

方式四:直接调用foreach API / stream的foreach也是一样
aList.forEach(System.out::print);

 

去重

利用java8 Stream流

已重写hashCode和equals

List<String> rebundList = aList.stream().distinct().collect(Collectors.toList());

自定义去重

collect(提供一个结束操作)

 

Collectors.toCollection(Supplier supplier)转Collection时提供Supplier进行排序操作

 

单属性去重

List<Student> collect = list.stream().collect(
    Collectors.collectingAndThen(
            Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(People::getName))), ArrayList::new));

多属性去重 通常以“;”进行隔开 

1 ArrayList<Student>  collect1 = list.stream().collect(collectingAndThen(
2         toCollection(() -> new TreeSet<>(
3                 Comparator.comparing(element -> element.getName() + ";" + element.getAge()))), ArrayList::new));

 

排序

Collections工具:Collections.sort(numbers);

 Stream流:

Stream<T> sorted(Comparator<? super T> comparator);

 

 

 1     /**
 2      * stream流排序 sort
 3      */
 4     @Test
 5     public void methodNameTest() {
 6         List<People> peopleList = getPeopleList();
 7         // 正序
 8         List<People> sortPeople = peopleList.stream()
 9                 .sorted(Comparator.comparing(People::getName)).collect(Collectors.toList());
10         // [People(name=张三, age=23), People(name=张三, age=20), People(name=李四, age=20)]
11         System.out.println(sortPeople);
12         // 反序 reversed()
13         List<People> reSortPeople = peopleList.stream()
14                 .sorted(Comparator.comparing(People::getName).reversed()).collect(Collectors.toList());
15         // [People(name=李四, age=20), People(name=张三, age=23), People(name=张三, age=20)]
16         System.out.println(reSortPeople);
17         // 组合排序 comparing().thenComparing()
18         List<People> combineSort = peopleList.stream()
19                 .sorted(Comparator.comparing(People::getName).thenComparing(People::getAge)).collect(Collectors.toList());
20         // [People(name=张三, age=20), People(name=张三, age=23), People(name=李四, age=20)]
21         System.out.println(combineSort);
22     }

 

LinkedList

List 接口的双向链接列表实现
允许所有元素(包括 null)
允许将链接列表用作堆栈、队列或双端队列。
在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端),除非要查找的元素在表头,否则要遍历整个表。
基本操作方法和ArrayList一致
 

LinkedList和ArrayList的区别?

ArrayList是实现了基于动态数组的结构,而LinkedList则是基于实现链表的数据结构。而两种数据结构在程序上体现出来的优缺点在于增删和改查的速率
 
数据的更新和查找
ArrayList连续存储,LinkedList散列存储,查询Arraylist快
ArraylList更新找到对应索引直接更新,LinkedList需要遍历到指定位置后更改前后指针的指向,更新Arraylist快
数据的增加和删除
Arraylist增加和删除要对剩下的元素进行移位(除了在末尾进行增添之外),LinkedList直接更改插入或者删除位置的前后指针,单就操作而言,LinkedList快
但是一般情况都是在末尾进行增删,而且Arraylist的增删底层调用的copyOf()被优化过,加之现在CPU对内存可以进行块操作,所以综合来说还是Arraylist快
 

Vctor

1、是一个老集合,一般不用了
2、大多数操作和ArrayList相同,区别就是vector是线程安全
3、当插入和删除频繁的时候(末尾操作除外),使用LinkedList;
Vector总是要比ArrayList慢,因为有同步(线程安全)
public synchronized boolean add(E e) {..}
4.增加元素的空间的时候,容量会进行2倍的扩展要比ArrayList扩展容量要大
5.底层数据结构是数组结构

除了Vctor,还有什么线程安全的List?

可以⽤Collections来将ArrayList来包装⼀下,变成线程安全,现在直接用JUC,后面出来的java并发工具包
JUC里面有个CopyOnWriteArrayList,用到写时复制机制,底层是通过复制数组的⽅式来实现的
通过创建副本给写操作,既保持了写的独立性,又保持了读的共享性

 add()机制:

get()⽅法⼜或是size()⽅法只是获取array所指向的数组的元素或者⼤⼩,读不加锁,写加锁

优点:

对于一些读多写少的数据,写入时复制的做法就很不错,例如配置、黑名单、物流地址等变化非常少的数据。
CopyOnWriteArrayList 并发安全且性能比 Vector 好。Vector 是增删改查方法都加了synchronized 来保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而 CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于 Vector。
 
缺点:
数据一致性问题。这种实现只是保证数据的最终一致性,不能保证数据的实时一致性;在添加到拷贝数据而还没进行替换的时候,读到的仍然是旧数据
内存占用问题。在进行写操作的时候,内存里会同时驻扎两个对象的内存,如果对象比较大,频繁地进行替换会消耗内存,从而引发 Java 的 GC 问题,这个时候,我们应该考虑其他的容器,例如 ConcurrentHashMap
 

 

 Set

Set接口继承了Collection接口。Set集合中不能包含重复的元素,每个元素必须是唯一的。你只需将元素加入set中,重复的元素不会添加

 

 HashSet

 
此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素
在创建迭代器之后,如果对 set 进行修改,除非通过迭代器自身的 remove 方法,否则在任何时间以任何方式对其进行修改,Iterator 都将抛出 ConcurrentModificationException。
 API与List基本一致
增加

 

 删除

 

 

 

 遍历
与List类似,同属Collection集合,有Stream流支持,也有直接foreach()。
 
Hashset去重原理
Hashset <自定义类对象>去重要重写hashcode()和equals()
Set调用 add 方法时,调用了添加对象的 hashCode方法和 equals 方法:如果Set集合中没有与该元素 hashCode 值相同的元素,则说明该元素是新元素,可以添加到 Set 集合中;如果两个元素的 hashCode 值相同,再使用 equals 方法比较,如果返回值为 true,就说明两个元素相同,新元素不会被添加到 Set 集合中;如果 hashCode 值相同,但是 equals 方法比较后,返回值为 false ,就说明两个元素不相同,新元素会被添加到 Set 集合中。

一般来说,一个类必要时会同时重写hashCode()和equals()两个方法(如果没有重写,那么就默认调用Object中的hashCode()和equals())。这也是HashSet去重机制的关键。

HashSet排序

hashset是无序的(即使自定义类实现Comparable且重写compareto也不能保证正确排序),对其排序有两种方式:
 1:转成List进行排序
 2:转成TreeSet进行排序
  Treeset是有序的,进行排序
        方式一:自然排序—自定义类必须实现Comparable接口且重写    compareto方法
        方式二:定义比较器—定义一个类实现Conparator接口,覆盖compare方法
       
 compareto、compare都是通过返回值为负、0、正来判断大小
 
在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后用这个(元素的hashcode)%(HashMap集合的大小)+1计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
内部使用HashMap来存储数据,数据存储在HashMap的key中,value都是同一个默认值

 

java.lnag.Object中对hashCode的约定:

1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果
3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能
 
 

 LinkedHashSet

 

可预知迭代顺序,怎么存就怎么取
哈希表和链接列表实现
插入速度低于HashSet,但是迭代访问全部元素性能很好
插入顺序不 受在 set 中重新插入的 元素的影响。(如果在 s.contains(e) 返回 true 后立即调用 s.add(e),则元素 e 会被重新插入到 set s 中。)

 

TreeSet

 

 

TreeSet 的去重是由所add对象声明的compareTo 决定的,而HashSet 会先去比较对象的hashcode 方法返回值,如果相同,再去比较对象的equals方法。因此一般可以通过改写对象的hashcode 和equals方法来修改对象的判重规则。

基于 TreeMap 的 NavigableSet 实现
使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法
也可以保证元素的唯一性

TreeSet<Object>底层自动去重,自定义类实现Compareable接口,重写compareTo()方法
 
构造:需要传入比较器

 

 

构造一个TreeSet
public static void testName03(){
        TreeSet<String>  list= new TreeSet(new Compareby());
        list.add("aaa");
        list.add("a");
        list.add("aa");
        list.add("aaaa");
        System.out.println(list);
    }
class Compareby  implements Comparator<String>{
    //o1大于o2 返回正确 ,相等 返回 0  负整数  表示 o1小于o2
    @Override
    public int compare(String o1, String o2) {
        
        int i=     o1.length()-o2.length();//主要的按照长度来进行判断条件
        
        return i==0 ? o1.compareTo(o2):i;//通过内容来进行对比次要的条件
    }
    

 

LinkedHashSet与hashset

LinkedHashSet是hashset的子类,hashset中有默认对Object对象类的hashcode和equals

LinkedHashSet继承自HashSet,源码更少、更简单,唯一的区别是LinkedHashSet内部使用的是LinkHashMap。这样做的意义或者好处就是LinkedHashSet中的元素顺序是可以保证的,也就是说遍历序和插入序是一致的

 

 

 

 

posted on 2023-02-09 14:45  or追梦者  阅读(39)  评论(0编辑  收藏  举报