内容学习于:edu.aliyun.com


1. Arrays类

  数组排序: java.til.Arrays.sort()。 实际上Arrays类就是属于java.util 包中的一个类并且sort()方法是一个static方法,Arrays 类之中的全部方法都是static方法,所以这个类的构造方法肯定是被私有化了,在Arrays 类之中支持有如下的一些常用操作方法:

  • <mark>数组排序:public static void sort(数据类型[] a)</mark>
  • <mark>【依赖排序】二分查找:public static int binarySearch(数据类型[] a,数据类型 key)</mark>
  • 【依赖排序】数组比较:public static int compare(数据类型[] a, 数据类型[] b)
    返回三个类型:大于(1),小于(-1),等于(0)
  • 【依赖排序】数组相等判断:public static boolean equals(数据类型[] a, 数据类型[] a2)
  • 数组填充:public static void fill(数据类型[] a, 数据类型val)
  • <mark>转成字符串:public static String toString(数据类型[] a)</mark>

观察数组填充:

public class JavaAPIDemo {
    public static void main(String[] args) {
        int data [] = new int[10];
        Arrays.fill(data,3);//对数组内容进行填充处理
        System.out.println(Arrays.toString(data));
    }
}

结果:

[3, 3, 3, 3, 3, 3, 3, 3, 3, 3]

  在Arrays类里面提供有一个最为重要的操作方法:二分查找法,主要的功能是确定要查找的数据是否存在,<mark>二分查找的核心本质在于数组必须是排序后的结果。</mark>

二分查找代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        int data [] = new int[]{1,5,7,2,3,6,0};//数组
        Arrays.sort(data);//数组默必须升序排序(默认)
        System.out.println(Arrays.binarySearch(data,3));//返回数据的索引
    }
}

结果:

3

  如果你现在的数据量本身并不大,也就只有20个数据,那么即便时间复杂度为n,那么也没有多慢,但是如果你现在的数据量非常大,大概有200个数组内容,那么这个时候就会发现循环要执行200次。所以为了提升数据的查询性能,可以借助于二分查找法进行处理。。
  <mark>传统的查找,需要依次比较,时间复杂度为n,时间复杂度为log(n),是最为理想的状态。</mark>

二分查找源码:

private static int binarySearch0(Object[] a, int fromIndex, int toIndex,
                                     Object key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;//采用移位的计算
            @SuppressWarnings("rawtypes")
            Comparable midVal = (Comparable)a[mid];
            @SuppressWarnings("unchecked")
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found.
    }

移位和整除比较:

public class JavaAPIDemo {
    public static void main(String[] args) {
        int data [] = new int[]{1,5,7,2,3,6,0,13,16,19};//数组
        Arrays.sort(data);//数组默必须升序排序(默认)[0, 1, 2, 3, 5, 6, 7, 13, 16, 19]
        int low = 0;
        int high = data.length-1;
        System.out.println((low+high)/2);//整除
        System.out.println((low+high)>>>1);//移位处理
    }
}

结果:

5
5

<mark>面试题:请编写一个二分查找法实现数组内容的查询</mark>

  在Arrays类中是有所提供的,使用的是binarySearch(),而这个查找的方式默认采用的是比较器的处理模式来完成的,那么下面可以按照同样的思路,通过循环的方式来处理,但是一旦通过循环的方式就只能够使用基本数据类型以确定大小关系了
  11 的2进制数据:00000000 00000000 00000000 00001011
  无条件向右移1位: 00000000 00000000 00000000 00000101 转为十进制是5,相当于整除除2
  使用2进制处理的最直接优势在于:速度更快,但是在大内存的情况下 ,这样的性能提升是有限的,但是所有不起眼的操作才可能造成性能的提升

简单二分查找改写代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        int data [] = new int[]{1,5,7,2,3,6,0,13,16,19};//数组
        Arrays.sort(data);//数组默必须升序排序(默认)[0, 1, 2, 3, 5, 6, 7, 13, 16, 19]
        for (int i :data){
            System.out.println(ArrayUtil.binarySearch(data,i));
        }
        
    }
}

class ArrayUtil{
    private ArrayUtil(){}

    public static int binarySearch(int data[],int key){
        int low = 0;//左边的索引
        int high = data.length-1;//右边的索引
        while (low<=high){//可能数据在边界
            int mid = (low + high)>>>1;//找到中间值
            if (data[mid]>key){//如果数据比中间的数小
                high = mid -1;//数据在左边
            }
            else if (data[mid]<key){
                low = mid+1;
            }
            else {//找到了
                return mid;
            }
        }
        return -1;//找不到
    }
}

结果:

0
1
2
3
4
5
6
7
8
9

  在实际的开发之中,这种代码你可能永远不会写到,因为如果真有此需求,直接通过现有的功能实现即可,但是笔试之中容易问到。

2. UUID类

  UUID是一种生成无重复字符串的一 种程序类,==这种程序类的主要功能是根据时间戳实现一个自动的无重复的字符串定义。==一般在获取UUID的时候往往都是随机生成一的个内容,所以可以通过如下方式获取:

  • 获取UUID对象:public static UUID randomUUID()

  • 根据字符串获取UUID内容:public static UUID fromString(String name)

    代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        UUID uid = UUID.randomUUID();
        System.out.println(uid.toString());
    }
}

结果:

ee5ab7e2-96b0-4c12-b3a0-a37c03656c6e

  <mark>在对一些文件进行自动命名处理的情况下,UUID类型非常好用。</mark>

3. Optional类

  <mark>Optional类的主要功能是进行null的相关处理</mark>,在以前进行程序开发的时候,如果为了防止程序之中现空指向异常,往往可以追加有null的验证。

传统引用传递问题代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        MessageUtil.useMessage(null);
    }
}


interface IMessage {
    String getContent();
}


class MessageImpl implements IMessage {
    @Override
    public String getContent() {
        return "www.mldn.cn";
    }
}


class MessageUtil {
    private MessageUtil() {
    }

    public static IMessage geIMessage() {
        return new MessageImpl();
    }

    public static void useMessage(IMessage msg) {
        if (!(msg == null))
            System.out.println(msg.getContent());//可能会出现null,导致空指向异常
    }
}

  在引用接收的一方往往都是被动的进行判断,所以为了解决这种被动的处理操作,在Java类中提供有一个Optional的类,这个类可以实现null 的处理操作,在这个类里面提供有如下的一些操作方法:

  • 返回空数据:public static Optional empty()
  • 获取空数据:public T get()
  • 保存数据,但是不允许出现空:public static Optional of(T value)
    • 如果为空,则抛出NullPointerException
  • 保存数据,允许为空:public static Optional ofNullable(T value)
  • 空的时候返回其他数据:public T orElse(T other)

  操作如下图所示:

修改程序代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        IMessage iMessage = MessageUtil.geIMessage().get();//获取数据
        MessageUtil.useMessage(iMessage);
    }
}


interface IMessage {
    String getContent();
}


class MessageImpl implements IMessage {
    @Override
    public String getContent() {
        return "www.mldn.cn";
    }
}


class MessageUtil {
    private MessageUtil() {
    }

    public static Optional<IMessage> geIMessage() {
        return Optional.of(new MessageImpl());//有对象
    }

    public static void useMessage(IMessage msg) {
        if (!(msg == null))
            System.out.println(msg.getContent());//可能会出现null,导致空指向异常
    }
}

结果:

www.mldn.cn

  如果说现在数据保存的内容是null,则就会在保存处出现异常,如下图所示:

  由于Optional类中允许保存有null 的内容,所以在数据获取的时候也可以进行null的处理。但是如果为null,则在使用get()获取数据的时候就会出现“Exception in thread “main” java : util . NoSuchElementException: No value present"异常信息,所以此时可以更换为orElse()方法。

空的处理:

public class JavaAPIDemo {
    public static void main(String[] args) {
        IMessage iMessage = MessageUtil.geIMessage().orElse(new MessageImpl());//获取数据
        MessageUtil.useMessage(iMessage);
    }
}

  <mark>在所有引用数据类型的操作处理之中,null 是一个重要的技术问题,所以JDK 1.8 后提供的这个新的类对于null的处理很有帮助,同时也是在日后进行项目开发之中使用次数很多的一个程序类。</mark>

4. ThreadLoad类

  在真正去了解ThreadLocal类作用的时候下面编写一个简单的程序做一个先期的分析。

代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        Message msg = new Message();//实例化消息主体对象
        msg.setInfo("www.mldn.cn");//设置消息发送内容
        Channel.setMessage(msg);//设置要发送的消息
        Channel.send();//发送消息
    }
}


class Channel {//消息发送的通道
    private static Message message;

    public static void send() {//发送消息
        System.out.println("【消息发送】" + message.getInfo());
    }

    public static void setMessage(Message message) {
        Channel.message = message;
    }
}

class Message {//要发送的消息体
    private String info;

    public void setInfo() {
        this.info = info;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}

结果:

【消息发送者C消息发送】、要发送的第三个消息
【消息发送者A消息发送】、要发送的第三个消息
【消息发送者B消息发送】、要发送的第二个消息

  如图所示:

  对于当前的程序实际上采用的是一种单线程的模式来进行处理的。那么如果在多线程的状态下能否实现完全一致的操作效果呢?为此将启动三个线程进行处理。

多线程的影响代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            Message msg = new Message();//实例化消息主体对象
            msg.setInfo("要发送的第一个消息");//设置消息发送内容
            Channel.setMessage(msg);//设置要发送的消息
            Channel.send();//发送消息
        }, "消息发送者A").start();
        new Thread(() -> {
            Message msg = new Message();//实例化消息主体对象
            msg.setInfo("要发送的第二个消息");//设置消息发送内容
            Channel.setMessage(msg);//设置要发送的消息
            Channel.send();//发送消息
        }, "消息发送者B").start();
        new Thread(() -> {
            Message msg = new Message();//实例化消息主体对象
            msg.setInfo("要发送的第三个消息");//设置消息发送内容
            Channel.setMessage(msg);//设置要发送的消息
            Channel.send();//发送消息
        }, "消息发送者C").start();

    }
}


class Channel {//消息发送的通道
    private static Message message;

    public static void send() {//发送消息
        System.out.println("【" + Thread.currentThread().getName() + "消息发送】、" + message.getInfo());
    }

    public static void setMessage(Message message) {
        Channel.message = message;
    }
}

class Message {//要发送的消息体
    private String info;

    public void setInfo() {
        this.info = info;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}

结果:

【消息发送者C消息发送】、要发送的第三个消息
【消息发送者A消息发送】、要发送的第三个消息
【消息发送者B消息发送】、要发送的第二个消息

  这个时候消息的处理产生了影响,如下图所示:
  在保持Channel (所有发送的通道)核心结构不改变的情况下,需要考虑到每个线程的独立操作问题。那么在这样的情况下就发现对于Channel类而言除了要保留有发送的消息之外,还应该多存放有一个每一一个线程的标记(当前线程),那么这个时候就可以通过ThreadLocal类来存放数据。在ThreadLocal类里面提供有如下的操作方法:

  • 构造方法:public ThreadLocal()
  • 设置数据:public void set(T value)
  • 取出数据:public T get()
  • 删除数据:public void remove()

  如下图所示:

修改后的代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            Message msg = new Message();//实例化消息主体对象
            msg.setInfo("要发送的第一个消息");//设置消息发送内容
            Channel.setMessage(msg);//设置要发送的消息
            Channel.send();//发送消息
        }, "消息发送者A").start();
        new Thread(() -> {
            Message msg = new Message();//实例化消息主体对象
            msg.setInfo("要发送的第二个消息");//设置消息发送内容
            Channel.setMessage(msg);//设置要发送的消息
            Channel.send();//发送消息
        }, "消息发送者B").start();
        new Thread(() -> {
            Message msg = new Message();//实例化消息主体对象
            msg.setInfo("要发送的第三个消息");//设置消息发送内容
            Channel.setMessage(msg);//设置要发送的消息
            Channel.send();//发送消息
        }, "消息发送者C").start();

    }
}


class Channel {//消息发送的通道
    private static final ThreadLocal<Message> MESSAGE_THREAD_LOCAL = new ThreadLocal<>();

    public static void send() {//发送消息
        System.out.println("【" + Thread.currentThread().getName() + "消息发送】、" + MESSAGE_THREAD_LOCAL.get().getInfo());
    }

    public static void setMessage(Message message) {
        MESSAGE_THREAD_LOCAL.set(message);

    }
}

结果:

【消息发送者B消息发送】、要发送的第二个消息
【消息发送者C消息发送】、要发送的第三个消息
【消息发送者A消息发送】、要发送的第一个消息

  <mark>每一个线程通过ThreadLocal只允许保存一个数据。</mark>

5. 定时调度

  <mark>定时器的主要操作是进行定时任务的处理</mark>,就好比你们每天早晨起来的铃声-样。在Java中提供有定时任务函数支持,但是这种任务的处理只是实现了一种间隔触发的操作。
  如果要想实现定时的处理操作主要需要有一个定时操作的主体类,以及一个定时任务的控制。可以使用两个类实现:

  • java.util.TimerTask 类:实现定时任务处理
  • java.util.Timer类:实现任务的启动,启动的方法:
    • 任务启动:public void schedule(TimerTask task,long delay)、延迟时间为毫秒
    • 间隔触发:public void scheduleAtFixedRate(TimerTask task,long delay,long period)

实现定时任务处理代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        //100毫秒后执行,间隔1秒执行一次
        timer.scheduleAtFixedRate(new MyTask(), 100, 1000);
    }
}


class MyTask extends TimerTask {//任务主体

    @Override
    public void run() {//多线程处理方法
        System.out.println(Thread.currentThread().getName() + "、定时任务开启,当前时间是:" + System.currentTimeMillis());
    }
}

结果:

Timer-0、定时任务开启,当前时间是:1579394654503
Timer-0、定时任务开启,当前时间是:1579394655504
Timer-0、定时任务开启,当前时间是:1579394656504
……

  如下图所示:

  <mark>这种定时是由JDK最原始的方式提供的支持,但是实际上开发之中利用此类方式进行的定时处理实现的代码会非常的复杂。</mark>

6. Base64加密工具

  正常来讲加密基本上永远都要伴随着解密,所谓的加密或者是解密往往都需要有一些所谓的规则。在JDK1.8开始提供有一组新的加密处理操作类,Base64 处理,在这个类里面有两个内部类:

  • Base64.Encoder:进行加密处理;
      加密:public byte[] encode(byte[] src)
  • Base64.Decoder:进行解密处理。
      解密:public byte[] decode(String src)

  虽然Base64可以实现加密与解密的处理,但是其由于是一个公版的算法,所以如果直接对数据进行加密往往并不安全,那么最好的做法是<mark>使用盐值操作。</mark>

解密和解密代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        String msg = "www.mldn.cn";
        String encMsg = new String(Base64.getEncoder().encode(msg.getBytes()));//加密
        System.out.println(encMsg);
        String oldMsg = new String(Base64.getDecoder().decode(encMsg));//解密
        System.out.println(oldMsg);
    }
}

结果:

d3d3Lm1sZG4uY24=
www.mldn.cn

使用盐值操作代码:

public class JavaAPIDemo {
    public static void main(String[] args) {
        String salt = "mldnjava";//盐值
        String msg = "www.mldn.cn" + "{" + salt + "}";
        String encMsg = new String(Base64.getEncoder().encode(msg.getBytes()));//加密
        System.out.println(encMsg);
        String oldMsg = new String(Base64.getDecoder().decode(encMsg));//解密
        System.out.println(oldMsg);
    }
}

结果:

d3d3Lm1sZG4uY257bWxkbmphdmF9
www.mldn.cn{mldnjava}

  即便现在有盐值实际上发现加密的效果也不是很好,<mark>最好的做法是多次加密。</mark>

复杂加密代码:

class StringUtil {
    private static final String SALT = "mldnjava";//公共盐值
    private static final int REPEAT = 5;//加密的次数

    /** * 加密处理 * * @param str 需要加密的字符串 * @return加密后的字符串 */
    public static String encode(String str) {
        String temp = str + "{" + SALT + "}";//盐值不对外公布
        byte[] data = temp.getBytes();//将字符串转换为字节
        for (int i = 0; i < REPEAT; i++) {//加密处理
            data = Base64.getEncoder().encode(data);
        }
        return new String(data);
    }

    /** * 解密处理 * @param str 需要解密的字符串 * @return 解密后去掉盐值的字符串 */

    public static String decode(String str) {
        byte[] data = str.getBytes();
        for (int i = 0; i < REPEAT; i++) {
            data = Base64.getDecoder().decode(data);
        }
        return new String(data).replaceAll("\\{\\w+\\}", "");
    }

}

public class JavaAPIDemo {
    public static void main(String[] args) {
        String str = StringUtil.encode("www.mldn.cn");
        System.out.println(str);
        System.out.println(StringUtil.decode(str));
    }
}

结果:

VjJ0U1QyRXdNSGRsU0ZKT1YwVTFhRlZ1Y0ZOTlZtUlZVMVJHVDAxcmNGb***V1F3WVZkS1dWRnRPV0ZTZWtaSVZERkZPVkJSUFQwPQ==
www.mldn.cn

<mark>最好的做法是使用2-3种加密程序,同时再找到一些完全不可解密的加密算法。</mark>