Java的集合体系及相关数据结构---Collection系列

​​​​​​​

其中,有序和无序是指存取时候的顺序一致或者不一致; 

Collection是单列集合的顶层接口,它的方法全部单列集合可用。 

Collection方法

方法名说明
boolean add(E e)添加元素
boolean remove(Object o)从集合中移除指定的元素
boolean removeIf(Object o)根据条件进行移除
void clear()清空集合中的元素
boolean contains(Object o)判断集合中是否存在指定的元素
boolean isEmpty()判断集合是否为空
int size()集合的长度,也就是集合中元素的个数

演示:

        Collection<String> collection = new ArrayList<>();
        //往List添加数据这个方法永远返回true
        //往set添加可能因为已经存在从而返回false
        collection.add("aaaaaaa");
        //清除集合全部元素
        collection.clear();
        //通过对象删除
        System.out.println(collection.remove("aaaaaaa"));
        //判断是否包含元素
        //要注意,要是集合装的是自定义对象,若想用contains方法,需要重写
        //alt ins 快速重写
        System.out.println(collection.contains("aaaaaaa"));
        //判断是否为空
        System.out.println(collection.isEmpty());

遍历方式

迭代器遍历

迭代器不依赖索引

注意:迭代器用完指针不复位;且迭代器遍历时候不可用集合的方法增删,只能用迭代器的方法;next()方法其实干了两件事:返回值并移动指针;要配合hasNext和next方法使用

        Collection<String> c = new ArrayList<>();

        //添加元素
        c.add("hello");
        c.add("world");
        c.add("java");
        c.add("javaee");

        Iterator<String> iterator = c.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }

增强for遍历

  • 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
  • 实现Iterable接口的类才可以使用迭代器和增强for
  • 所有单列集合以及数组才能用
  • 集合名字.for快捷生成
        Collection<String> c = new ArrayList<>();

        //添加元素
        c.add("hello");
        c.add("world");
        c.add("java");
        c.add("javaee");

        for(String s :c){
            System.out.println(s);
        }

注意:在循环中修改增强for的第三方变量(就比如说上面的s) 不会改变集合值


Lambda表达式遍历 

简化写法的遍历方式,这种方式尤为简洁,底层还是增强for遍历

 public static void main(String[] args) {
        /* 
        lambda表达式遍历:
                default void forEach(Consumer<? super T> action):
        */

        //1.创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("zhangsan");
        coll.add("lisi");
        coll.add("wangwu");
        //2.利用匿名内部类的形式
        //底层原理:
        //其实也会自己遍历集合,依次得到每一个元素
        //把得到的每一个元素,传递给下面的accept方法
        //s依次表示集合中的每一个数据
       /* coll.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/

        //lambda表达式
        coll.forEach(s -> System.out.println(s));
    }

List集合的方法

首先,针对上述方法,由于List是Collection的继承接口,故都能用,但由于它自己有索引,所以多了不少额外的方法:

方法名描述
void add(int index,E element)在此集合中的指定位置插入指定的元素
E remove(int index)删除指定索引处的元素,返回被删除的元素
E set(int index,E element)修改指定索引处的元素,返回被修改的元素
E get(int index)返回指定索引处的元素

 其中,对于add方法,若原位置有元素,则不会覆盖,会往后移动,相当于insert;

而对于remove有两种方式,一种是通过删除索引对应的元素,一种是通过数据值删除元素,当list存储的是Integer对象时,不加说明会按照索引来删除。

 public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        //method1(list);
        //method2(list);
        //method3(list);
        //method4(list);
    }

    private static void method4(List<String> list) {
        //        E get(int index)		返回指定索引处的元素
        String s = list.get(0);
        System.out.println(s);
    }

    private static void method3(List<String> list) {
        //        E set(int index,E element)	修改指定索引处的元素,返回被修改的元素
        //被替换的那个元素,在集合中就不存在了.
        String result = list.set(0, "qqq");
        System.out.println(result);
        System.out.println(list);
    }

    private static void method2(List<String> list) {
        //        E remove(int index)		删除指定索引处的元素,返回被删除的元素
        //在List集合中有两个删除的方法
        //第一个 删除指定的元素,返回值表示当前元素是否删除成功
        //第二个 删除指定索引的元素,返回值表示实际删除的元素
        String s = list.remove(0);
        System.out.println(s);
        System.out.println(list);
    }

    private static void method1(List<String> list) {
        //        void add(int index,E element)	在此集合中的指定位置插入指定的元素
        //原来位置上的元素往后挪一个索引.
        list.add(0,"qqq");
        System.out.println(list);
    }

遍历方式

//创建集合并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");

//1.迭代器
/*Iterator<String> it = list.iterator();
     while(it.hasNext()){
        String str = it.next();
        System.out.println(str);
}*/


//2.增强for
//下面的变量s,其实就是一个第三方的变量而已。
//在循环的过程中,依次表示集合中的每一个元素
/* for (String s : list) {
       System.out.println(s);
   }*/

//3.Lambda表达式
//forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
//并把每一个元素传递给下面的accept方法
//accept方法的形参s,依次表示集合中的每一个元素
//list.forEach(s->System.out.println(s) );


//4.普通for循环
//size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素
/*for (int i = 0; i < list.size(); i++) {
            //i:依次表示集合中的每一个索引
            String s = list.get(i);
            System.out.println(s);
        }*/

// 5.列表迭代器
//获取一个列表迭代器的对象,里面的指针默认也是指向0索引的

//额外添加了一个方法:在遍历的过程中,可以添加元素
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
    String str = it.next();
    if("bbb".equals(str)){
        //qqq
        it.add("qqq");
    }
}
System.out.println(list);

不同的是,有一个有一个List专有的列表迭代器ListIterator,这个继承于Iterator,有自己的add和remove方法。

这几种遍历方法都能达到目的,但是想遍历时候修改建议用迭代器或者列表迭代器方式;当然for也可以,只不过得整个索引;单纯遍历用增强for或者lambda(通用);

List的实现类

首先得了解数据结构;

什么是数据结构? -数据结构是计算机存储、组织数据的方式,为了更好的管理和使用数据,结合具体需求来选择;

栈:后进先出,先进后出;

数据ABCD依次进栈,进完后,效果如图所示:

其中,D称为栈顶元素,A称为栈底元素;

出栈则从栈顶开始依次出去;这就是先进后出的含义;

Java中,方法执行时候就是这么干的;

 队列:先进先出,后进后出

同样地,ABCD依次进队,完成后效果如图:

因此出队列方式就从A开始向上依次出去;

数组:查询快,增删慢 

查询快是因为只要有数组地址和索引,就可以快速定位;

增删慢则是因为每次增加和删除,其后位置的数据都要移位,所以慢;

链表:数据独立,查询慢,增删快

链表就和绳子打结类似。每一个节点既存了数据,又存了下一个元素的地址,还有它本身的地址;

创建一个链表:

因此对于链表的增删只需把它前一个记录的地址值或者它自己记录的地址值改掉即可,相对效率就快了;

除此之外,还有一种双向链表,查询效率更高一些:

ArrayList 自动扩容原理

方法这些不说了,详见之前的ArrayList集合

  1. 创建ArrayList对象的时候,若使用空参构造,则他在底层先创建了一个长度为0的数组,数组名字:elementDate,定义变量size。
  2. 添加第一个元素时候,会创建一个新的长度为10的数组。
  3. 存满后,还要继续,就要扩容,长度为原来的1.5倍。再存满,再扩容1.5倍,循环;
  4. 若一次用addAll方法添加多个元素,超过了15,那就以实际添加数据的长度为准; 

LinkedList集合

这个底层就是双链表。LinkedList是有索引的,我看网上说没有,我还去验证了一下:

        LinkedList<String> strings = new LinkedList<>();
        strings.add("aaa");
        strings.add("bbb");
        strings.add("ccc");
        strings.add("ddd");
        for (int i = 0; i < strings.size(); i++) {
            System.out.print(strings.get(i)); //aaabbbcccddd
        }

但是呢,这个索引本质上还是用next实现的,不像数组那样可以高效读取;

它本身提供的方法不常用,用父接口的方法就好 


泛型

泛型,以ArrayList举例,那个<>就是装的泛型,用来指明数据类型;早期是没有泛型的。

现在,Java中的泛型是伪泛型,你在写代码时候指明了泛型,但是编译完会进行擦除,即泛型擦除;更多的,泛型在现在是用来标志作用;

  • 泛型只能存引用数据类型;
  • 指明泛型后,仍然可以传递其子类型(不常用);
  • 不写泛型,默认Object;

泛型的定义 

泛型类:

当使用某个类时候,某个变量的数据类型不确定,就可以定义泛型类:

修饰符 class 类名<类型>{

}

自定义泛型类示例:

public class MyArrayList<E> {
    Object[] objects = new Object[10];
    int size;

    //这个E相当于是指代前面提到的不确定类型 E
    public boolean add(E e){
        objects[size] = e;
        size++;
        return true;
    }

    public E get(int index){
        return (E)objects[index];
    }

    public String toString(){
        return Arrays.toString(this.objects);
    }

使用自定义泛型类:

        MyArrayList<String> myArrayList = new MyArrayList<>();
        myArrayList.add("aaa");
        myArrayList.add("bbb");
        myArrayList.add("ccc");
        System.out.println(myArrayList.toString());
 泛型方法创建
    //不确定的类型E要写在修饰符最后面
    public static <E> void addAll(ArrayList<E> arrayList,E e1,E e2,E e3,E e4) {
        arrayList.add(e1);
        arrayList.add(e2);
        arrayList.add(e3);
        arrayList.add(e4);
    }

调用: 

        ArrayList<String> list =new ArrayList<>();
        ListUtil.addAll(list,"aaa","bbb","ccc","ddd");
        System.out.println(list);

 泛型方法有缺陷,即不管你是什么类型,只要是E,方法就会执行,从而产生错误,比如泛型方法里往一个数组添加数据时候。

为了解决这种问题,出现了泛型通配符即类似:

public static void method(ArrayList<? extends A> list){

}

 <? extends A>就是泛型通配符,表示这个参数只能为A及其子类;

把extends换成super就表示A及其父类;

泛型接口

这个就在名字后面加<E>就行了;

具体使用方式要么在实现类中给出具体类型,要么在实现类的对象创建时候明确类型

泛型练习:

        /*
            需求:
                定义一个继承结构:
                                    动物
                         |                           |
                         猫                          狗
                      |      |                    |      |
                   波斯猫   狸花猫                泰迪   哈士奇


                 属性:名字,年龄
                 行为:吃东西
                       波斯猫方法体打印:一只叫做XXX的,X岁的波斯猫,正在吃小饼干
                       狸花猫方法体打印:一只叫做XXX的,X岁的狸花猫,正在吃鱼
                       泰迪方法体打印:一只叫做XXX的,X岁的泰迪,正在吃骨头,边吃边蹭
                       哈士奇方法体打印:一只叫做XXX的,X岁的哈士奇,正在吃骨头,边吃边拆家

            测试类中定义一个方法用于饲养动物
                public static void keepPet(ArrayList<???> list){
                    //遍历集合,调用动物的eat方法
                }
            要求1:该方法能养所有品种的猫,但是不能养狗
            要求2:该方法能养所有品种的狗,但是不能养猫
            要求3:该方法能养所有的动物,但是不能传递其他类型
         */

 答案在附件里,没写全,写了一个举例;


Set系列

先了解数据结构。

把子节点放大,你会得到:

  • 每一个节点的子节点数量称为度;
  • 上述结构又可称为二叉树;
  • 树的层数又叫树高;
  • 最上面的节点称为根节点; 
  • 跟18连一块的节点称为左子树;同理,右边的叫右子树;


二叉查找树

添加规则:小的往左,大的往右,一样的不存; 

弊端: 

根节点选的不合适会导致瘸腿,树高太高,查找无疑是不方便的;为了解决这种问题,引入平衡二叉树


平衡二叉树

规则:任意左右子树高度差不超过1

上面这个就不是,10的左右子树长度不一致;

下面这俩就是;

平衡二叉树旋转机制 

左旋:

旋转之后:

稍微复杂的情况:

 ↓

右旋 

  ↓

复杂情况: 

  ↓

四种选择左右旋的情形:

下图这种情况进行一次右旋即可;

 下面这种情况先局部使用左旋,让他变成上图情况,最后使用右旋即可;

 剩下两种情况与上述两种情况刚好相反,不再赘述;

红黑树

红黑树规则 


二叉树遍历方式(二叉树都可以用)

前序遍历

中序遍历 

后序遍历

*20在最后,图没截全 

层序遍历

Set系列集合

特点:

  • 不可以存储重复元素没有索引
  • 不能使用普通for循环遍历
  • 可以使用Collection方法

add方法的返回值要关注,因为不能有重复值,不一定添加成功; 

        Set<String> set = new TreeSet<>();
      	//添加元素
        set.add("ccc");
        set.add("aaa");
        set.add("aaa");
        set.add("bbb");
      
      	//遍历集合
        Iterator<String> it = set.iterator();
        while (it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }
        System.out.println("-----------------------------------");
        for (String s : set) {
            System.out.println(s);
        }

HashSet

Hash值:对象的整数表现形式

  • 定义在Object类中,所有对象可用,默认根据地址值计算;
  • 一般需要重写hashCode方法
  • 部分情况下,不同地址值计算出来的Hash值有可能相同,称为Hash碰撞,概率较小;
  • 存储自定义对象一定要重写hashcode和equals方法
  • LinkedHashSet有序; 
  • 有数据去重需求时候使用HashSet;

TreeSet 

不必学习它的额外方法,Collection提供的就够了;因为它是基于红黑树的,所以他的存取效率都比较高,而且默认排序,0的时候就代表认为这两个对象相同;

        //创建集合对象
        TreeSet<Integer> ts = new TreeSet<Integer>();

        //添加元素
        ts.add(10);
        ts.add(40);
        ts.add(30);
        ts.add(50);
        ts.add(20);

        ts.add(30);

        //遍历集合
        for(Integer i : ts) {
            System.out.println(i);
  •  对于数值类型,比如Integer,Double等,按照大小排序;
  • 对于String,Char按照字符在ASCII表中的数字升序排序;若字符串较长,先比第一个字母,第一个相同比第二个,依次往后推

对于自定义对象,排序方法有两种,一种是实现Comparable接口,称为默认排序或者自然排序;重写的方法和之前那个快速排序法有点类似: 

public class Student implements Comparable<Student>{
    private String name;
    private int age;

    public Student() {
    }

    public Student(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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        //按照对象的年龄进行排序
        //主要判断条件: 按照年龄从小到大排序
        int result = this.age - o.age;
        //次要判断条件: 年龄相同时,按照姓名的字母顺序排序
        result = result == 0 ? this.name.compareTo(o.getName()) : result;
        return result;
    }
}
public static void main(String[] args) {
        //创建集合对象
        TreeSet<Student> ts = new TreeSet<>();
	    //创建学生对象
        Student s1 = new Student("zhangsan",28);
        Student s2 = new Student("lisi",27);
        Student s3 = new Student("wangwu",29);
        Student s4 = new Student("zhaoliu",28);
        Student s5 = new Student("qianqi",30);
		//把学生添加到集合
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
		//遍历集合
        for (Student student : ts) {
            System.out.println(student);
        }
    }

 第二种方式是比较器排序

这种适用于Java中已经写好的类比如String,它自己重写的CompareTo方法不满足需求了,就要用到Comparator对象;

这种就是匿名内部类的实现方式,可以改写成lambda表达式:

	//创建集合对象
        TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
            @Override
            public int compare(Teacher o1, Teacher o2) {
                //o1表示现在要存入的那个元素
                //o2表示已经存入到集合中的元素
              
                //主要条件
                int result = o1.getAge() - o2.getAge();
                //次要条件
                result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
                return result;
            }
        });
		//创建老师对象
        Teacher t1 = new Teacher("zhangsan",23);
        Teacher t2 = new Teacher("lisi",22);
        Teacher t3 = new Teacher("wangwu",24);
        Teacher t4 = new Teacher("zhaoliu",24);
		//把老师添加到集合
        ts.add(t1);
        ts.add(t2);
        ts.add(t3);
        ts.add(t4);
		//遍历集合
        for (Teacher teacher : ts) {
            System.out.println(teacher);
        }

单列集合的选择:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值