7. 集合(存储引用类型)

主要有三种集合:

  • List: 有序集合,可以放重复的数据
  • Set:无序集合,不能重复
  • Map:无序集合,键值对

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CK4j5u8o-1612419431401)(C:UsersLiTangMMAppDataRoamingTyporatypora-user-imagesimage-20191202110439361.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uaXFAq5S-1612419431402)(C:UsersLiTangMMAppDataRoamingTyporatypora-user-imagesimage-20191202111325514.png)]

Collection接口

public class CollectionTest(){
    boolean add(E element);
    void clear();
    boolean isEmpty();
    int size();
    Object[] toArray();
    
    Iterator<E> iterator(); //返回集合类依赖的迭代器对象
    boolean contains(Object o); //调用集合元素equals方法比较
    boolean remove(Object o); 
    //建议:使用迭代器删除集合元素
    // 增强for循环
    
}

泛型

一种未知的数据类型,当我们不知道用什么类型的时候,可以使用泛型

泛型也可以是个变量,用来接收数据类型

好处:

  • 避免了类型转换的麻烦,存储的是什么类型,去除的就是出什么类型
  • 把运行期异常提升到了编译期

在创建对象时,会把数据类型作为参数传递,确定数据类型

// 泛型类
pubilc class GenericClass<E> {
    private E name;
    public E getName(){
        return name;
    }
    public void setName(E name){
        this.name = name;
    }
}
// 泛型方法
// 定义在修饰符和返回类型之间
public class GenericMethod{
    public <M> void method01(M m){
        sout(m);
    }
    public static <M> void method02(M m){
        sout(m)
    }
}
// 泛型接口
//...

//泛型通配符 ?

//ArrayList<Interger>:ArrayList<?>:ArrayList<String>
public static void printArray(ArrayList<?> list){
    Interator<?> it = list.iterator();
    while(it.hasNext()){
        Object o = it.next();
        sout(0);
    }
}

//泛型型上下限
//泛型没有继承
<? extends E>:代表使用的泛型只能是E类型的子类或自己
<? super E>:只能是E类型的父类或自己

mainclass{
    main{
        GenericClass<String> gc = new GenericClass<>();
        GenericClass<Integer> gc = new GenericClass<>();
        GenericMethod gcc = new GenericMethod();
        gcc.method("nihao");
        gcc.method(123);
    }
}

List集合

List 接口

java.util.List extends Coolection

特点:

  • 有序的集合
  • 有索引
  • 允许存储重复的元素

特有方法(与索引有关)注意索引越界异常:

List Methods{
    public void add(int index,E element);
    public E get(int index);
    public E remove(int index);
    public E set(int index,E element);
}

ArrayList集合

底层是数组,线程不安全的

添加时会复制原数组再添加

LinkedList集合

底层是双向链表(有首尾指针),线程不安全的

LinkedList methods{
    public E getFist();
    public E getLast();
    public void addFirst(E e);
    public E removeFirst();
    public void addLast(E e);
    public E removeLast();
    public push(E e);
    public E pop();
}

Vector集合

可以实现可怎张的对象数组,与ArrayList类似,线程安全的

Set集合

Set接口

java.util.Set extends Collection

  1. 不允许存储重复的元素(元素重写了hashCode equals方法)
  2. 没有索引,无带索引的方法

add方法会调用对象的hashCode方法,计算哈希值,在集合中找有没有改哈希值的元素,没有则存储;有则调用equals方法,查找是否有相等的元素,没有则存储.

HashSet集合

java.util.HashSet implements Set

哈希表结构,无序

jdk1.8版本之前 哈希表=数组+链表

jdk1.8版本之后 哈希表=数组+红黑树(链表长度超过8个元素自动转换,提高查询速度)

LinkedHashSet集合

java.util.LinkedHashSet implements HashSet

底层是一个哈希表+链表. 多了一条链表,保证元素有序

可变参数(数组)

jdk1.5之后出现的新特性

使用前提:

​ 当方法的参数列表已经确定,但是参数个数不确定,就可以使用可变参数.

注意事项:

  1. 一个方法的参数列表,只能有一个可变参数.
  2. 如果方法的参数有多个,可变参数必须写在参数列表的末尾
public class Demo01VarArgs{
    public static void main(String[] args){
        add(1,2,3,4,5); //将会创建一个长度为5的数组
    }
    
    public static int add(int...arr){
        int res = 0;
        for(int var:arr){
            res += var;
        }
        return res
    }
}

Map集合

双列集合

常用子集

HashMap: 存数据采用的哈希表结构元素的存取顺序不能保证一致. 由于要保证键的唯一 不重复,需要重写简单hashCode()方法 equals()方法

LinkedHashMap: HahsMap下的一个子类,采用哈希表接口+链表结构

常用方法

public put(K key,V value)

public remove(Object key)

public get(Object key)

public Set<K> keySet()

public Set<Map.Entry<K,V>> entrySet()

package demo.hashmap;

import java.util.HashMap;

public class learnTest {
    public static void main(String[] args) {
        String s = "aabbccddeeffaabbddddefda";
        HashMap<Character,Integer> map = new HashMap<>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if(map.containsKey(c)){
                map.put(c,map.get(c).intValue()+1);
            }else{
                map.put(c,1);
            }
        }
        for (Character key:map.keySet()) {
            System.out.println(key+":"+map.get(key));
        }
    }
}

8. 多线程

并发与并行

并发:指多个事件在同一时间发生(单核)

并行:至多个事件在同一时刻发生(多核)

线程

创建多线程程序的第一种方式:创建Thread类的子类

package demo.Thread;
/*
    创建多线程程序的第一种方式:创建Threa的子类
    java.lang.Thread类:是描述线程的类,要继承Thread类

    实现步骤:
        1. 创建一个Thred类的子类
        2. 在Thread类的子类中重写Thread的run方法,设置线程任务
        3. 创建Thread类的子类对象
        4. 调用start方法,开启新的线程去执行run方法
            结果1是两个线程并发执行
            多次启动一个线程是非法的.特别是当线程已经执行结束后,不能再重新启动
        java程序属于抢占式调度,哪个优先级包
 */
public class Demo01Thread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("main:"+i);

        }
    }
}

package demo.Thread;
import java.lang.Thread;

public class MyThread extends Thread {

    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("thread:"+i);
        }
    }
}

获取线程的名称

  • 使用Thread类中的getName方法
  • 可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程的名称

​ String getName() : 返回线程的名称

​ static Thread currentThread() : 返回对当前正在执行的线程对象的引用

  • 默认名称:

​ 主线程: main 其他: Thread-0,Thread-1,Thread-2

设置线程名称

  • 使用Thread类中的方法setName(名字)

    • void setName(String name) 改变线程名称
  • 创建一个带参数的构造函数,参数传递线程的名字;调用父类的带参数构造方法

    • Thread(String name)

Thread类中常用方法

public class Thread{
    public Thread();
    public Thread(String name);
    public Thread(Runnable target);
    public Thread(Runnable target,String name);
    
    public String getName();
    public void start(); //外部调用
    public void run(); // 需要重写
    public static void sleep(long millis); //暂停指定毫秒数
    public static Thread currentThread(); 
}

创建线程方式二:

  1. 定义Runnable接口的实现类,并重写该方法的run()方法
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象
  3. 调用Thread对象的start方法启动线程

    public class MyRunnable implements Runnable{
        @Override
        public void run(){
            for(int i = 0;i < 20;i++){
                sout(i);
            }
        }
    }
    
    public class test{
    
        public static void main(String[] args){
            MyRunnable mr = new MyRunnable();
            Thread th = new Thread(mr);
            th.start();
    
    
        }
    }

Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享. 但是如果实现了Runable接口的话,则容易实现资源共享.

优势:

  1. 适合多个相同的程序代码线程共享同一个资源
  2. 可以避免java中的单继承的局限性
  3. 增加线程的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类

匿名内部类实现线程的创建

作用:简化代码

public class Demo{
    public static void main(String[] args){
        new Thread(){
            @Override
            public void run(){
                   //动作
            }
        }.start();
        
        Runnable r = new Runnable(){
            @Override
            public void run(){
                //动作
            }
        };
        new Thread(r).start();
        
        new Thread(
                new Runnable(){
                @Override
                public void run(){
                    //动作
                }
            }
        ).start();
    }
}

线程安全

多个线程同时访问共享资源

解决线程安全的方法:

  1. 使用同步代码块

    synchronized(锁对象){
        可能出现线程安全的代码;
    }

注意:

  1. 通过代码块中的锁对象,可以使用任意对象
  2. 但是必须保证多个线程使用的锁对象是同一个
  3. 作用:把同步代码块锁住,只让一个线程在同步代码块中执行

原理:

    1. 使用了锁对象,也叫对象监视器
    2. 线程抢夺cpu执行权,遇到synchronize代码块,如果检查到锁对象,则会获取锁对象,知道代码块代码执行完成.
    1. 使用同步方法

    ​ 把访问了共享数据的代码抽取出来,作为一个新的方法,使用synchronized修饰.

    ​ 锁对象为this.

    public synchronized ....{
        //共享数据
    }

    使用静态同步方法

    锁对象不是this(静态方法优先于对象), 是本类的class属性.

    1. 使用Lock

    java.util.concurrent.locks.Lock接口

    Lock接口的方法:

    ​ void lock() 获取锁

    ​ void unlock() 释放锁

    java.util.concurrent.locks.ReentrantLock implements Lock

    使用步骤:

    1. 在成员位置创建一个ReentrantLock对象
    2. 在可能出现线程安全的代码前调用lock
    3. 在可能出现线程安全的代码后调用unlock

    线程状态

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nOEQTM1E-1612419431404)(C:UsersLiTangMMAppDataRoamingTyporatypora-user-imagesimage-20191212095514418.png)]

    Object.wait()

    Object.notify()

    Thread.sleep()

    等待唤醒

    public class Test {
        public static void main(String[] args) {
            // 共同锁对象
            Object object = new Object();
            Producer producer = new Producer(object);
            Consumer consumer = new Consumer(object);
    
            consumer.start();
            producer.start();
        }
    }
    
    public class Consumer extends Thread{
        private Object lockobj;
        @Override
        public void run() {
            while (true){
                synchronized (lockobj){
                    System.out.println("顾客线程获取锁对象");
                    System.out.println("顾客:我需要买东西(进入等待状态)");
                    // 进入等待状态
                    try {
                        lockobj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 被唤醒后执行
                    System.out.println("顾客被唤醒,执行wait后的代码");
                    System.out.println("顾客:这是我要的东西,谢谢!");
                }
            }
        }
    
        Consumer(Object object){
            super();
            lockobj = object;
        }
    }
    
    public class Producer extends Thread{
        private Object lockobj;
    
        @Override
        public void run() {
            while(true){
                synchronized (lockobj){
                    System.out.println("老板获取锁对象,等待5秒");
                    // 等待5秒
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("老板:东西准备好了(唤醒顾客)");
                    lockobj.notify();
                }
                // 保证顾客拿到锁
                try {
                    System.out.println("------------");
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        Producer(Object object){
            super();
            lockobj = object;
        }
    }

    等待唤醒机制

    线程间通信

    等待唤醒机制

    如果能获取锁,线程就从WAITING状态变成RUNNABLE状态

    否则,从wait set出来,又进入entry set,线程进入阻塞状态.

    调用wait和notify方法注意:

    1. wait方法和notify方法必须要由同一个锁对象调用. 因为: 对应的锁对象可以通过notify唤醒使用同一个锁对象调用wait方法后的线程
    2. wait方法与notify方法是属于Object类的方法. 因为: 锁对象可以是任意对象, 而任意对象的所属类都是继承了Object类的.
    3. wait方法与notify方法必须要在同步代码块或者同步函数中使用,因为: 必须要通过锁对象调用这2个方法.

    线程池

    底层实现LinedList<Thread>

    创建线程池:

    java.util.concurrent.Executors

    static ExecutorService newFixedThradPoll(int nThreads)

    返回一个接口对象,通过调用接口方法获取线程

    sumbit(Runnable task): 提交一个Runnable任务用于执行

    shutdown() : 关闭/销毁线程池

    使用步骤:

    1. 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
    2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
    3. 调用ExecutorService中的submint,传递线程任务,开启线程

    9. Lambda表达式

    lambda表达式的标准格式

    由三部分组成:

    1. 一些参数
    2. 箭头
    3. 一行代码

    格式:

    ​ (参数列表) -> (一些重写方法的代码)

    解释说明:

    ​ ():接口中抽象方法的参数列表

    ​ ->: 传递的意思

    ​ {}: 重写接口的抽象方法的方法体

    lambda简写

    10. File

    文件和目录路径的抽象表示形式

    File类的两个静态变量

    public class File{
        static String pathSeparator; //路径分割符 win; linux:
        static String separator; //文件名称分隔符 win\ linux/
    }

    绝对路径 相对路径

    File类的构造方法

    public class File{
        public File(String pathstring);
        // 只封装File对象,不考虑路径的真假情况
        public File(String parent, String child);
        // parent:父路径 child:子路径
        public File(File parent, String child);
        // parent为File类,可使用File的方法对路径进行一些操作
    }

    File类常用方法--获取

    public class File{                      // 文件        文件夹
        public String getAbsolutePath();    // 绝对路径     绝对路径
        public String getPath();            // 构造路径     构造路径
        public String getName();            // 文件名称     目录名称    
        public long lenght();               // 文件大小(B)  目录大小(0B)
    }

    File类常用方法--判断

    public class File{
        public boolean exits();
        public boolean isDirectory();
        public boolean isFile();
    }

    File类常用方法--创建删除

    public class File{
        public boolean createNeaFile();
        public boolean delete();
        public boolean mkdir();
        public boolean mkdirs();
    }

    目录遍历

    public class File{
        public String[] list();
        public File[] listFiles():
    }

    文件过滤器


    转载至CSDN博主:[李唐敏民]

    最后修改:2021 年 02 月 18 日 09 : 31 PM
    如果觉得我的文章对你有用,请随意赞赏