Java设计模式之单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式用来保证一个类仅有一个实例,并提供一个访问他的全局访问点;主要解决了一个全局使用类的频繁创建与销毁;

注意事项:

  • 单例类只能有一个实例

  • 单例类必须自己创建自己唯一的实例

  • 单例类必须给所有其他对象提供这一实例

单例模式从实现方式上可以分为饿汉式单例模式和懒汉式单例模式,饿汉式单例模式是线程安全的,懒汉式单例模式需要考虑线程安全性,实现线程安全的懒汉式单例模式一般有两种实现内部类式和双重检查式;

(1)饿汉式 – 线程安全

// 饿汉式单例
public class Singleton1 {
 
    // 指向自己实例的私有静态引用,主动创建
    private static Singleton1 singleton1 = new Singleton1();
 
    // 私有的构造方法
    private Singleton1(){}
 
    // 以自己实例为返回值的静态的公有方法,静态工厂方法
    public static Singleton1 getSingleton1(){
        return singleton1;
    }
}

饿汉式线程安全的原因:

因为在类加载时就实例化了,即使多个线程同时获取它,取到的都是类加载时实例化的那个变量的值;

(2)懒汉式-线程不安全

// 懒汉式单例
public class Singleton2 {
 
    // 指向自己实例的私有静态引用
    private static Singleton2 singleton2;
 
    // 私有的构造方法
    private Singleton2(){}
 
    // 以自己实例为返回值的静态的公有方法,静态工厂方法
    public static Singleton2 getSingleton2(){
        // 被动创建,在真正需要使用时才去创建
        if (singleton2 == null) {
            singleton2 = new Singleton2();
        }
        return singleton2;
    }
}

懒汉式线程不安全的原因:

多个线程同时使用时,例如第一个线程判断完if(single==null)为空时,进入了if的代码中,还没开始new对象时,这时第二个线程可能也正好判断为空进来了if,这时就会创造出两个实例。

(3)线程安全的懒汉式-内部类方式

public class Singleton {
    //静态私有内部类
    private static class InnerClass {
        private final static Singleton instance = new Singleton();
    }
 
    private Singleton(){
 
    }
 
    public static Singleton getInstance(){
        return InnerClass.instance;
    }
}

线程安全的原因:

同样使用了类加载机制,这个会延迟加载,因为Singleton加载了,但内部类InnerClass类没有被主动调用,只有显式的调用getInstance()方法对象才会被加载;

(4)线程安全的懒汉式-双重检查方式

public class Singleton {
 
    // volatile: 防止指令重排序
    private volatile static Singleton instance;
 
    private Singleton() {
 
    }
 
    public static Singleton getInstance() {
        // 第一次检查
        if(instance == null){
            // 只在最初几次会进入该同步块,提高效率
            synchronized(Singleton.class){
                // 第二次检查
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

一般建议使用饿汉式方式;

参考文献:菜鸟教程|单例模式

数据结构(四)链表

之前我们学习了动态数组、栈、队列,这些底层依托的是静态数组,并没有实现真正的动态,今天我们学习的这个数据结构是真正的自己实现了动态的特性,那就是链表;

链表的数据存储在节点中,首先我们看节点的定义

class Node{
    E e;
    Node next;
}

从上面我们可以看出,链表中还有一个next的节点类型的对象,它是干嘛的呢,如果一个节点的next是null,那么就说明此节点是链表的最后一个节点。

优点:真正的动态,不需要处理固定容量的问题

缺点:丧失了随机访问的能力

下面我们先定义节点

package com.wjy329;
/**
 * @author: wjy329
 * @create: 2018-10-09 15:58
 **/
public class LinkedList<E> {
    private class Node{
        public E e;
        public Node next;

        public Node(E e,Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e,null );
        }

        public Node(){
            this(null,null);
        }
        @Override
        public String toString(){
            return e.toString();
        }
    }
}

节点有了之后那就开始定义链表

package com.wjy329;
/**
 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-10-09 15:58
 **/
public class LinkedList<E> {
    private class Node{
        public E e;
        public Node next;

        public Node(E e,Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e,null );
        }

        public Node(){
            this(null,null);
        }
        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node head;
    private int size;
    public LinkedList(){
        head = null;
        size = 0;
    }
    //获取链表中的元素个数
    public int getSize(){
        return size;
    }
    //返回列表是否为空
    public boolean isEmpty(){
         return size ==0;
    }
}

上面就是链表的定义,接下来我们就要逐步的完善它,实现各种相关功能。

在链表头添加元素:

//在链表头添加新的元素
    public void addFirst(E e){
        Node node = new Node(e);
        node.next = head;
        head = node;
        // 下面这一行代码也可以解决添加头的问题
//      head = new Node(e,head);
        size++;
    }

上面代码写了两种方式,第一种方式代码多,但是比较直观,很容易就看明白了,第二种方式代码少,但是不容易理解,这里详细说一下第二种方式,第二种利用了节点的构造函数,新建一个节点,新的节点值为传入的e,而next指向head节点,这就意味着该节点的下一个节点就是此时的head节点,然后我们把此时的节点再作为头节点即可。

在链表中间添加元素:

image.png

我们先看上图,要在索引为2的节点前插入节点,我们先需要将新的节点的next指向此时的2节点,然后1节点的next指向插入的节点即可,代码表示为: node.next = prev.next  prev.next = node; 

接下来我们看第三行图,这个表示了错误的顺序,先将1节点的后驱节点指向要插入的节点,然后再将插入节点的后驱指向1节点的后驱,这时我们发现1节点的后驱节点指向的是要插入的节点,就已经出现错误了,所以要注意插入的顺序

下面看具体代码:

//在链表的index位置处添加新的元素
public void add(int index,E e){
    if(index < 0 || index > size){
        throw new IllegalArgumentException("添加失败,非法的位置");
    }
    if(index == 0){
        addFirst(e);
    }else {
        Node prev = head;
        for(int i = 0;i < index - 1;i++){
            prev = prev.next;
        }
        Node node = new Node(e);
        node.next = prev.next;
        prev.next = node;
        size++;
    }
}
//在链表末尾处添加元素
public void addLast(E e){
    add(size,e);
}

链表虚拟头节点:

上面的代码我们还需判断是否为头节点,为了方便添加并且统一代码,我们需要给列表设置一个虚拟的头节点,头节点仅仅是为了方便指向位置,节点内容为空。修改完成后的代码如下:

package com.wjy329;
/**
 * @author: wjy329
 * @create: 2018-10-09 15:58
 **/
public class LinkedList<E> {
    private class Node{
        public E e;
        public Node next;

        public Node(E e,Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e,null );
        }

        public Node(){
            this(null,null);
        }
        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node dummyHead;
    private int size;
    public LinkedList(){
        dummyHead = new Node(null,null);
        size = 0;
    }
    //获取链表中的元素个数
    public int getSize(){
        return size;
    }
    //返回列表是否为空
    public boolean isEmpty(){
         return size ==0;
    }
    //在链表头添加新的元素
    public void addFirst(E e){
       add(0,e);
    }
    //在链表的index位置处添加新的元素
    public void add(int index,E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("添加失败,非法的位置");
        }
        Node prev = dummyHead;
        for(int i = 0;i < index;i++){
            prev = prev.next;
        }
        Node node = new Node(e);
        node.next = prev.next;
        prev.next = node;
        size++;

    }
    //在链表末尾处添加元素
    public void addLast(E e){
        add(size,e);
    }
}

链表的操作:

下面有些操作并不是链表常使用的操作,仅仅作为练习使用。

查找:

//获取链表第index个位置的元素
public E get(int index){
    if(index < 0 || index > size){
        throw new IllegalArgumentException("非法的位置");
    }
    Node cur = dummyHead.next;
    for(int i = 0;i < index;i++){
        cur = cur.next;
    }
    return cur.e;
}
//获取链表的第一个元素
public E getFirst(){
    return get(0);
}
//获取链表的最后一个元素
public E getLast(){
    return get(size - 1);
}

更新:

//修改第index位置的元素
public void set(int index,E e){
    if(index < 0 || index > size){
        throw new IllegalArgumentException("非法的位置");
    }
    Node cur = dummyHead.next;
    for(int i = 0;i < index;i++){
        cur = cur.next;
    }
    cur.e = e;
}

判断是否存在:

//查找链表中是否存在e
public boolean contains(E e){
    Node cur = dummyHead.next;
    while (cur != null){
        if(cur.e.equals(e)){
            return true;
        }
        cur = cur.next;
    }
    return false;
}

删除:

//删除index位置的元素
public E remove(int index){
    if(index < 0 || index > size){
        throw new IllegalArgumentException("非法的位置");
    }
    Node prev = dummyHead;
    for(int i = 0;i < index;i++){
        prev = prev.next;
    }
    Node retNode = prev.next;
    prev.next = retNode.next;
    retNode.next = null;
    size--;
    return retNode.e;
}


// 从链表中删除元素e
public void removeElement(E e){

    Node prev = dummyHead;
    while(prev.next != null){
        if(prev.next.e.equals(e))
            break;
        prev = prev.next;
    }

    if(prev.next != null){
        Node delNode = prev.next;
        prev.next = delNode.next;
        delNode.next = null;
    }
}

关于链表的实现就写到这里,当然我们可以用链表来实现栈和队列,以后有时间再写吧。

数据结构(三)栈和队列

栈是一种线性结构,相比数组,栈对应的操作是数组的子集,栈只能从一端添加元素,也只能从一端取出元素,这一端称为栈顶。栈是一种后进先出的数据结构,从下图我们可以看出,先入栈的元素在栈底,得等到它之后的所有元素都出去,它才能出栈,反而最后入栈的元素可以先出栈。

1538361372515527.png

栈的应用:

  • 撤销操作

  • 程序调用的系统栈

栈的实现:

栈的实现很简单,我们需要实现5个操作即可,void push(E)-入栈操作、E pop()-出栈操作、E peek()-查看栈顶元素、int getSize()-查看栈里一共有多少元素、boolean isEmpty()-判断栈是否为空;  下面栈代码的实现用了上篇文章中写的数组,为了获取栈顶元素,我们需要在上篇数组的代码中添加两个方法,获取数组最后一个元素(即栈顶)和第一个元素(栈底),具体方法如下:

//获取数组最后一个元素
public E getLast(){
    return get(size -1);
}
//获取数组第一个元素
public E getFirst(){
    return get(0);
}

我们先来一个栈的接口,最后实现栈:

Stack.java:

package com.wjy329;

/**
 * @Author wjy329
 * @Time 2018/10/19:27 PM
 * @description
 */
public interface Stack<E> {
    //获取栈中元素个数
    int getSize();
    //判断栈是否为空
    boolean isEmpty();
    //入栈
    void push(E e);
    //出栈
    E pop();
    //查看栈顶元素
    E peek();
}

ArrayStack.java:

package com.wjy329;
/**
 * @Author wjy329
 * @Time 2018/10/19:30 PM
 * @description
 */

public class ArrayStack<E> implements Stack<E>{

    Array<E> array;

    public ArrayStack(int capacity){
        array = new Array<>(capacity);
    }

    public ArrayStack(){
        array = new Array<>();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    @Override
    public void push(E e) {
        array.addLast(e);
    }

    @Override
    public E pop() {
        return array.delLast();
    }

    @Override
    public E peek() {
        return array.getLast();
    }

    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Stack:");
        res.append("[");
        for(int i=0;i < array.getSize();i++){
            res.append(array.get(i));
            if(i != array.getSize()-1){
                res.append(",");
            }
        }
        res.append("] top");
        return res.toString();
    }
}

文章一开始就说了两个栈的应用,在我们面试中,经常用到的栈的应有就有一个是括号的匹配,下面给出代码:

package com.wjy329;

import java.util.Stack;

/**
 * @Author wjy329
 * @Time 2018/10/110:07 PM
 * @description
 */

public class Solution {
    public boolean isValid(String s){
        Stack<Character> stack = new Stack<>();
        for(int i=0;i < s.length();i++){
            char c = s.charAt(i);
            if(c == '(' || c == '[' || c == '{'){
                stack.push(c);
            }else {
                if(stack.isEmpty()){
                    return false;
                }
                char topChar = stack.pop();
                if(c == ')' && topChar != '('){
                    return false;
                }
                if(c == ']' && topChar != '['){
                    return false;
                }
                if(c == '}' && topChar != '{'){
                    return false;
                }

            }
        }
        return stack.isEmpty();
    }
}

括号匹配的思路也很简单:就是将给定的字符串依次取出如果是左括号则入栈,右括号则对括号进行匹配。

队列

队列也是一种线性结构,相比数组,栈对应的操作是数组的子集,只能从一端(队尾)添加元素,只能从另一端(队首)取出元素。队列是一种先进先出的数据结构。

image.png

队列的实现:

和栈的实现类似,同样我们需要5个方法,void enqueue(E)-入队、E dequeue()-出队、E getFront()-查看队首元素、int getSize()-查看队列中的元素个数、boolean isEmpty()-判断队列是否为空;

package com.wjy329;
/**
 * @Author wjy329
 * @Time 2018/10/110:44 PM
 * @description
 */

public class ArrayQueue<E> implements Queue<E>{

    Array<E> array;

    public ArrayQueue(int capacity){
        array = new Array<>(capacity);
    }

    public ArrayQueue(){
        array = new Array<>();
    }


    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    @Override
    public void enqueue(E e) {
        array.addLast(e);
    }

    @Override
    public E dequeue() {
        return array.delFirst();
    }

    @Override
    public E getFront() {
        return array.getFirst();
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue:");
        res.append("[");
        for(int i=0;i < array.getSize();i++){
            res.append(array.get(i));
            if(i != array.getSize()-1){
                res.append(",");
            }
        }
        res.append("] tail");
        return res.toString();
    }
}

循环队列:

上述数组队列当进行删除队首元素时,其后面的元素需要往前移,时间复杂度为O(n),循环队列不需要往前移动,当后面空间满时,可以往前在寻找空间,循环队列可以通过(tail+1)%c == front 来判断队列是否已满。tail-队尾、c-队列容量、front-队首;

package com.wjy329;
/**
 * @Author wjy329
 * @Time 2018/10/29:12 AM
 * @description
 */

public class LoopQueue<E> implements Queue<E>{

    private E[] data;
    private int front,tail;
    private int size;

    public LoopQueue(int capacity){
        data = (E[]) new Object[capacity + 1];
        front = 0;
        tail = 0;
        size = 0;
    }

    public LoopQueue(){
        this(10);
    }

    public int getCapacity(){
        return data.length - 1;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return front == tail;
    }

    @Override
    public void enqueue(E e) {
        if((tail + 1) % data.length == front){
            resize(getCapacity() * 2);
        }
        data[tail] = e;
        tail = (tail+1)%data.length;
        size++;
    }

    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity + 1];
        for(int i = 0;i < size;i++){
            newData[i] =data[(i + front) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
    }

    @Override
    public E dequeue() {
        if(isEmpty()){
            throw  new IllegalArgumentException("队列为空,不能出队");
        }
        E ret = data[front];
        data[front] = null;
        front = (front + 1)%data.length;
        size--;
        if(size == getCapacity()/4 && getCapacity()/2 != 0){
            resize(getCapacity()/2);
        }
        return ret;
    }

    @Override
    public E getFront() {
        if(isEmpty()){
            throw  new IllegalArgumentException("队列为空");
        }
        return data[front];
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue:");
        res.append("[");
        for(int i=front;i != tail;i = (i + 1)%data.length){
            res.append(data[i]);
            if((i + 1)%data.length != tail){
                res.append(",");
            }
        }
        res.append("] tail");
        return res.toString();
    }

}

数据结构(二)数组

从这篇开始就研究数组这个最基本的数据结构。

数组基础:

把数据码成一排进行存放

image.png

如上图所示,数组应该有一个自己的名字用来和其他数组区分,数组的索引是从0开始的,也就是想取到数组中的第三个数字,那么得取索引为2的值,即arr[2];

接下来说一下Java中的数组,Java数组中的每一个元素需要我们存放相同类型的元素,下面我们来演示一下Java数组的基本操作。

Java数组:

package com.wjy329;

public class Main {

    public static void main(String[] args) {
        //初始化一个int数组
       int[] scores = new int[10];
        //结果输出为: 0 0  0  0 0 。。。
        //默认的值都为0
       for(int i : scores){
           System.out.println(i);
        }
        //给数组赋值为数组的索引
        //打印结果:0 1 2 3 4 5 。。。
        for(int j=0;j < scores.length;j++){
           scores[j] = j;
            System.out.println(scores[j]);
        }
        
    }
}

当然,上面是最简单的Java数组的操作;

数组最大的优点是快速查询,可以直接根据索引查找,所以数组最好应用于“索引有语意”的情况。

Java为我们提供的数组不能进行添加和删除操作,下面我们来自己做一个我们自己的数组来实现此类功能;

封装我们自己的数组:

开始的时候,我们只需要简单的声明以及写出每个方法即可,最后我会将完整的封装的数组代码写上。

package com.wjy329;
/**
 * @description: 自定义数组
 * @author: wjy329
 * @create: 2018-09-30 17:04
 **/
public class Array {
    private int[] data;
    private int size;

    //构造函数,传入数组容量,size为数组中的元素个数
    public Array(int capacity){
        data = new int[capacity];
        size= 0;
    }
    //无参数的构造函数,数组容量为10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数组的容量
    public int getCapacity(){
        return data.length;
    }
    //返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
}

以上就是自定义数组的初始化和一些简单获取数组容量和元素个数的方法;我们对数组需要进行等操作,所以下面我们也将一一实现。

增:

//向所有元素后添加一个元素
public void addLast(int e){
    if(size == data.length){
        throw new IllegalArgumentException("添加元素失败,数组容量已满");
    }
    data[size] = e;
    size ++;
    //复用添加方法
    //add(size,e);
}
//在所有元素前添加一个元素e
public void addFirst(int e){
    add(0,e);
}

//在第index位置插入一个新元素e
public void add(int index,int e){
    if(size == data.length){
        throw new IllegalArgumentException("添加元素失败,数组容量已满");
    }
    if(index < 0 || index > size){
        throw new IllegalArgumentException("添加元素失败,插入位置过大或为负数");
    }
    for(int i = size - 1;i >= index;i --){
        data[i + 1] = data[i];
    }
    data[index]=e;
    size++;
}

代码很好理解,我主要的说在指定位置添加元素方法,我们从后往前依次向后移一位,直到指定位置停止;size-1 为最后一个数的索引值,然后将其后移一位,接下来再后移其他位置的数据;

删:

//从数组中删除index位置的元素,返回删除的元素
public int remove(int index){
    if(index < 0 || index >= size){
        throw new IllegalArgumentException("非法索引");
    }
    int del = data[index];
    for(int i = index + 1;i < size;i++){
        data[i-1] = data[i];
    }
    size--;
    return del;
}

上面是删除指定位置元素,删除的思路就是指定位置后的元素都向前移一位即可;下面是删除指定元素,需要用到后面的查找方法,查找到元素的索引,然后删除。

//删除数组中指定的元素
public void delElement(int e){
    int index = find(e);
    if(index != -1){
        remove(index);
    }
}

查、

//获取指定位置的元素
 int get(int index){
    if(index < 0 || index >= size){
        throw new IllegalArgumentException("非法索引");
    }
    return data[index];
}
//将index位置的元素换为e
void set(int index,int e){
    if(index < 0 || index >= size){
        throw new IllegalArgumentException("非法索引");
    }
     data[index] = e;
}

//查找数组中是否存在元素e
public boolean contains(int e){
    for(int i=0;i < size; i++){
        if(data[i] == e){
            return true;
        }
    }
    return false;
}

//查找数组中元素e所在的索引,如果不存在元素e,返回-1
public int find(int e){
    for(int i=0;i < size; i++){
        if(data[i] == e){
            return i;
        }
    }
    return -1 ;
}

 上面就是基本的思路,当然其中有些不足,比如相同元素的查找和删除等,上面的代码只会查找一个或者删除一个,并不会全部删除,这是因为主要演示的是思路和逻辑,会了上面的,后面的设计也就会显得轻车熟路了。

下面给上封装数组的完整代码:

Array.java:

package com.wjy329;
/**
 * @description: 自定义数组
 * @author: wjy329
 * @create: 2018-09-30 17:04
 **/
public class Array {
    private int[] data;
    private int size;

    //构造函数,传入数组容量,size为数组中的元素个数
    public Array(int capacity){
        data = new int[capacity];
        size= 0;
    }
    //无参数的构造函数,数组容量为10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数组的容量
    public int getCapacity(){
        return data.length;
    }
    //返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    //向所有元素后添加一个元素
    public void addLast(int e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        data[size] = e;
        size ++;
        //复用添加方法
        //add(size,e);
    }
    //在所有元素前添加一个元素e
    public void addFirst(int e){
        add(0,e);
    }

    //在第index位置插入一个新元素e
    public void add(int index,int e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        if(index < 0 || index > size){
            throw new IllegalArgumentException("添加元素失败,插入位置过大或为负数");
        }
        for(int i = size - 1;i >= index;i --){
            data[i + 1] = data[i];
        }
        data[index]=e;
        size++;
    }
    //获取指定位置的元素
     int get(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        return data[index];
    }
    //将index位置的元素换为e
    void set(int index,int e){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
         data[index] = e;
    }

    //查找数组中是否存在元素e
    public boolean contains(int e){
        for(int i=0;i < size; i++){
            if(data[i] == e){
                return true;
            }
        }
        return false;
    }

    //查找数组中元素e所在的索引,如果不存在元素e,返回-1
    public int find(int e){
        for(int i=0;i < size; i++){
            if(data[i] == e){
                return i;
            }
        }
        return -1 ;
    }

    //从数组中删除index位置的元素,返回删除的元素
    public int remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        int del = data[index];
        for(int i = index + 1;i < size;i++){
            data[i-1] = data[i];
        }
        size--;
        return del;
    }
    //删除数组中的第一个元素
    public int delFirst(){
        return remove(0);
    }
    //删除数组中的最后一个元素
    public int delLast(){
        return remove(size -1);
    }
    //删除数组中指定的元素
    public void delElement(int e){
        int index = find(e);
        if(index != -1){
            remove(index);
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array:size = %d,capacity = %d\n",size,data.length));
        res.append("[");
        for(int i=0;i < size;i++){
            res.append(data[i]);
            if(i != size - 1){
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }
}

上面仅仅是对int型数组的探索,为了让我们自定义的数组适合任意类型,我们将其定义为范型数组,下面直接给出代码:

package com.wjy329;
/**
 * @description: 自定义数组
 * @author: wjy329
 * @create: 2018-09-30 17:04
 **/
public class Array<E> {
    private E[] data;
    private int size;

    //构造函数,传入数组容量,size为数组中的元素个数
    public Array(int capacity){
        data = (E[])new Object[capacity];
        size= 0;
    }
    //无参数的构造函数,数组容量为10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数组的容量
    public int getCapacity(){
        return data.length;
    }
    //返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    //向所有元素后添加一个元素
    public void addLast(E e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        data[size] = e;
        size ++;
        //复用添加方法
        //add(size,e);
    }
    //在所有元素前添加一个元素e
    public void addFirst(E e){
        add(0,e);
    }

    //在第index位置插入一个新元素e
    public void add(int index,E e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        if(index < 0 || index > size){
            throw new IllegalArgumentException("添加元素失败,插入位置过大或为负数");
        }
        for(int i = size - 1;i >= index;i --){
            data[i + 1] = data[i];
        }
        data[index]=e;
        size++;
    }
    //获取指定位置的元素
     public E get(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        return data[index];
    }
    //将index位置的元素换为e
    public void set(int index,E e){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
         data[index] = e;
    }

    //查找数组中是否存在元素e
    public boolean contains(E e){
        for(int i=0;i < size; i++){
            if(data[i].equals(e)){
                return true;
            }
        }
        return false;
    }

    //查找数组中元素e所在的索引,如果不存在元素e,返回-1
    public int find(E e){
        for(int i=0;i < size; i++){
            if(data[i].equals(e)){
                return i;
            }
        }
        return -1 ;
    }

    //从数组中删除index位置的元素,返回删除的元素
    public E remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        E del = data[index];
        for(int i = index + 1;i < size;i++){
            data[i-1] = data[i];
        }
        size--;
        return del;
    }
    //删除数组中的第一个元素
    public E delFirst(){
        return remove(0);
    }
    //删除数组中的最后一个元素
    public E delLast(){
        return remove(size -1);
    }
    //删除数组中指定的元素
    public void delElement(E e){
        int index = find(e);
        if(index != -1){
            remove(index);
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array:size = %d,capacity = %d\n",size,data.length));
        res.append("[");
        for(int i=0;i < size;i++){
            res.append(data[i]);
            if(i != size - 1){
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }
}

动态数组:

我们使用上面的代码一段时间后发现,这个TM的还是静态数组啊,还是指定的大小没法扩充啊,还是毫无卵用啊。。。别急,接下来,我们就添加几行代码使其动态的扩展。

动态的思路也是很简单的,当我们添加元素时,如果发现已经满了,那么我们再新建一个比之前更大的数组(假设为2倍),然后将其值都赋值给新的数组,这样实现了添加的扩容;当我们删除到一定量的时候,我们发现太多的空间被浪费了,那么我们将其按照一定的比例缩小数组(当数组内元素个数为当前容量的一半时,我们就缩减为一半),以此来保证数组的空间合理性。

扩容的方法代码:

private void resize(int newCapacity) {
    E[] newData = (E[])new Object[newCapacity];
    for(int i= 0;i < size;i++){
        newData[i] = data[i];
    }
    data = newData;
}

最终代码:

package com.wjy329;
/**
 * @description: 自定义数组
 * @author: wjy329
 * @create: 2018-09-30 17:04
 **/
public class Array<E> {
    private E[] data;
    private int size;

    //构造函数,传入数组容量,size为数组中的元素个数
    public Array(int capacity){
        data = (E[])new Object[capacity];
        size= 0;
    }
    //无参数的构造函数,数组容量为10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数组的容量
    public int getCapacity(){
        return data.length;
    }
    //返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    //向所有元素后添加一个元素
    public void addLast(E e){
        if(size == data.length){
            throw new IllegalArgumentException("添加元素失败,数组容量已满");
        }
        data[size] = e;
        size ++;
        //复用添加方法
        //add(size,e);
    }
    //在所有元素前添加一个元素e
    public void addFirst(E e){
        add(0,e);
    }

    //在第index位置插入一个新元素e
    public void add(int index,E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("添加元素失败,插入位置过大或为负数");
        }
        if(size == data.length){
            resize(2 * data.length);
        }

        for(int i = size - 1;i >= index;i --){
            data[i + 1] = data[i];
        }
        data[index]=e;
        size++;
    }

    //获取指定位置的元素
     public E get(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        return data[index];
    }
    //将index位置的元素换为e
    public void set(int index,E e){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
         data[index] = e;
    }

    //查找数组中是否存在元素e
    public boolean contains(E e){
        for(int i=0;i < size; i++){
            if(data[i].equals(e)){
                return true;
            }
        }
        return false;
    }

    //查找数组中元素e所在的索引,如果不存在元素e,返回-1
    public int find(E e){
        for(int i=0;i < size; i++){
            if(data[i].equals(e)){
                return i;
            }
        }
        return -1 ;
    }

    //从数组中删除index位置的元素,返回删除的元素
    public E remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("非法索引");
        }
        E del = data[index];
        for(int i = index + 1;i < size;i++){
            data[i-1] = data[i];
        }
        size--;
        if(size == data.length/2){
            resize(data.length/2);
        }

        return del;
    }
    //删除数组中的第一个元素
    public E delFirst(){
        return remove(0);
    }
    //删除数组中的最后一个元素
    public E delLast(){
        return remove(size -1);
    }
    //删除数组中指定的元素
    public void delElement(E e){
        int index = find(e);
        if(index != -1){
            remove(index);
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array:size = %d,capacity = %d\n",size,data.length));
        res.append("[");
        for(int i=0;i < size;i++){
            res.append(data[i]);
            if(i != size - 1){
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }

    private void resize(int newCapacity) {
        E[] newData = (E[])new Object[newCapacity];
        for(int i= 0;i < size;i++){
            newData[i] = data[i];
        }
        data = newData;
    }
}

数据结构(一)初识

数据结构我们可能并不陌生,科班的同学应该都接触过这门课程,由于当初年少无知,认为数据结构不是那么重要,导致后来失去了很多机会,所以接下来我要系统的学习一下数据结构,也希望正在大学的朋友们不要轻视这门课程。

数据结构研究的是数据如何在计算机进行组织和存储,使得我们可以高效的获取数据或者修改数据。总的来说,数据结构可以分为三种:线性结构、树结构、图结构;

线性结构:数组、栈、队列、链表、哈希表…

树结构:二叉树、二叉搜索树、AVL、红黑树、Treap、Splay、堆、Trie、线段树、K-D树、并查集、哈夫曼树…

图结构:邻接矩阵、邻接表 

=====================================================

数据结构(二)数组

数据结构(三)栈和队列

数据结构(四)链表

目录待更新

Spring Cloud 学习(三)服务调用

服务调用者需要我们新建一个项目来调用,步骤和前面的client基本相同。

1.完整pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.wjy329</groupId>
   <artifactId>eureka-consumer</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>eureka-consumer</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.5.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
      <spring-cloud.version>Finchley.SR1</spring-cloud.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>

2.配置文件

application.properties:

spring.application.name=spring-cloud-consumer
server.port=9001
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

3.启动类

启动类需要添加@EnableDiscoveryClient@EnableFeignClients 注解

package com.wjy329.eurekaconsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;


@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class EurekaConsumerApplication {

   public static void main(String[] args) {
      SpringApplication.run(EurekaConsumerApplication.class, args);
   }
}

4.fegin调用

package com.wjy329.eurekaconsumer.service;


import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @Author wjy329
 * @Time 2018/9/2811:48 AM
 * @description
 */
@FeignClient(name= "wjy-client")
public interface HelloRemote {
    @RequestMapping(value = "/hello")
    public String hello(@RequestParam(value = "name") String name);
}
  • name是远程服务名,与服务提供者的名字需保持一致,方法需和服务提供者的controller方法名和参数个数保持一致。

5.controller

package com.wjy329.eurekaconsumer.controller;

import com.wjy329.eurekaconsumer.service.HelloRemote;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author wjy329
 * @Time 2018/9/2811:51 AM
 * @description
 */

@RestController
public class ConsumerController {

    @Autowired
    HelloRemote helloRemote;

    @RequestMapping("/hello/{name}")
    public String index(@PathVariable("name") String name) {
        return helloRemote.hello(name);
    }

}

到此,服务的注册与调用就基本完成了,我们开始按照注册中心、服务提供、服务消费的顺序依次启动程序。启动成功后,在浏览器输入:localhost:8080/hello?name=123  看到返回结果,说明服务提供者正常;

然后再输入 http://localhost:9001/hello/wjy  看到返回结果,说明调用远程服务成功。

image.png

Spring Cloud 学习(二)服务注册与发现

在第一篇的架构图中,我们可以看到最中间的是服务注册中心,毕竟作为微服务架构,服务提供者和消费者都得通过注册中心来工作,Spring Cloud中的注册中心是Spring Cloud Eureka

 1、Spring Cloud Eureka

  • 基于Netflix Eureka做了二次封装

  • 两个组件组成:

    – Eureka Server 注册中心

    – Eureka Client  服务注册

2、注册中心-Eureka Server

 接下来我们来建一个Eureka Server的项目;

在用idea构建时,我们选择eureka server即可

image.png

创建完成后,我们需要以下几步:

1、查看pom依赖

如果你是按照上述方法创建,那么依赖自动添加了,如果不是按上述方法创建,我们还需要在pom文件中添加相关的依赖,下面我给出完整的依赖,相关依赖在注释中写明。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.wjy329</groupId>
   <artifactId>eureka</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>eureka</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.5.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
      <spring-cloud.version>Finchley.SR1</spring-cloud.version>
   </properties>

   <dependencies>
      <!-- 需要添加的依赖 -->
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>

2、在启动类上添加@EnableEurekaServer 注解

package com.wjy329.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {

   public static void main(String[] args) {
      SpringApplication.run(EurekaApplication.class, args);
   }
}

3、配置文件

spring.application.name=eureka

server.port=8080
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
  • eureka.client.register-with-eureka :表示是否将自己注册到Eureka Server,默认为true。

  • eureka.client.fetch-registry :表示是否从Eureka Server获取注册信息,默认为true。

  • eureka.client.serviceUrl.defaultZone :设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认是http://localhost:8761/eureka ;多个地址可使用 , 分隔。

这几步完成后,我们启动项目,访问http://localhost:8080 即可。

image.png

3、服务注册-Eureka Client

 由于一段时间我们不需要更改上面server的操作,为了方便,所以我们将上面的server打包成jar包,运行后,方便我们使用。

用控制台进入我们的项目目录,然后执行 mvn clean package 命令,打包项目。

image.png

我这里修改了端口号,一般使用8761端口,然后进入项目的target目录下,执行java -jar 项目.jar 启动服务。

image.png

为了更加方便使用后台运行方式,在控制台中输入  nohup java -jar eureka-0.0.1-SNAPSHOT.jar > /dev/null 2>&1 & 

也可以成功启动注册中心。

和注册中心的步骤大致相同,如下建立项目

image.png

步骤大致也分3步:

1、查看pom依赖

此处pom.xml需要特别注意要加入web的依赖,不然就没有tomcat,就不会启动成功

web依赖:

<dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

pom完整依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.wjy329</groupId>
   <artifactId>eureka-client</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>eureka-client</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.5.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
      <spring-cloud.version>Finchley.SR1</spring-cloud.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
</project>

2、在启动类上加@EnableDiscoveryClient注解

package com.wjy329.eurekaclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {

   public static void main(String[] args) {
      SpringApplication.run(EurekaClientApplication.class, args);
   }
}

3、配置文件

#服务名字
spring.application.name=wjy-client
server.port=8080
#注册中心的注册地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

然后我们启动项目,在注册中心可以看到服务注册成功。

image.png

4、Eureka高可用(集群)

一个server确实是不可靠的,万一一个注册中心挂了,那么所有的服务都会没用,所有必须要学会集群。

4.1 双节点注册中心

说白了,就是两个注册中心,即使一个挂了,还有另一个扛着,哈哈。

方法:按照上面注册中心的方法,建两个注册中心,用端口号区分,然后相互注册即可。

1、两个注册中心的配置文件

application1.properties:

spring.application.name=eureka

server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8762/eureka/

application2.properties:

spring.application.name=eureka2

server.port=8762
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

2、服务提供者的配置文件

只需要将之前的配置中多添加一个注册中心的url即可:

spring.application.name=wjy-client
server.port=8080
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/

配置完成后,先启动两个注册中心,然后启动client,观察和测试效果。

image.png如图,分别在两个注册中心中都注册了服务。

4.2 集群注册中心

这里指三台及三台以上的情况,其实和双节点一样,在多个配置中心中,每个配置中心需要注册到除了自己外的其他注册中心,比如server1需要配置server2、server3、server4等,而server2需要配置server1、server3、server4等。

1、多个注册中心的配置文件

application1.properties:

spring.application.name=eureka

server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8762/eureka/,http://localhost:8763/eureka/

application2.properties:

spring.application.name=eureka2

server.port=8762
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8763/eureka/

application3.properties:

spring.application.name=eureka2

server.port=8763
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/

2、服务提供者的配置文件

需要加上所有的配置中心:

spring.application.name=wjy-client
server.port=8080
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/,http://localhost:8763/eureka/

自行测试结果。。。

Linux 命令学习值nohup用法

今天在学习Spring Cloud的时候遇到了nohup命令,这里记录一下。

说起nohup那么就不得不谈一谈&  

&后台运行,执行 ./shell.sh & 的时候, 即使使用Ctrl C,  .sh也会继续运行。 但是如果直接关掉控制台后,.sh的进程就会被杀掉

nohup : 运行nohup ./shell.sh 的时候, 关闭控制台, .sh进程还是存在的。但是如果使用Ctrl C, 那么.sh进程也是会被杀掉

综上,又想Ctrl C,又想直接关闭shell,那么可以结合使用,即: nohup ./xxx.sh &

下图是我的一个例子,用组合启动了java程序后,退出并关闭控制台,java程序还是运行的,进程还存在。

image.png

image.png

当我们再次打开控制台,输入命令查看进程,发现进程依然存在。

image.png

Spring Cloud 学习(一)微服务介绍

之前了解过Spring Cloud的一系列知识,但是没有做记录,现在从头开始学习一下,记录下来。

1、什么是微服务

1.1 微服务的提出

1.2 微服务原文

  • 一系列微小的服务共同组成

  • 跑在自己的进程里

  • 每个服务为独立的业务开发

  • 独立部署

  • 分布式的管理

1.3 应用架构的发展

dubbo-architecture-roadmap.jpg

单一应用架构=》垂直应用架构=》分布式服务架构=》流动计算架构

这是从dubbo官网弄下来的一张图,这张图可以直观的看出架构的发展。

1.4 分布式定义

旨在支持应用程序和服务的开发,可以利用物理架构由多个自治的处理元素不共享主内存,但通过网络发送消息合作。–Leslie Lamport

1.5 简单的微服务架构

image.png

1.6 微服务架构的基础框架/组件

  • 服务注册发现

  • 服务网关

  • 后端通用服务(也称为中间层服务)

  • 前端服务(也称为边缘服务)

1.7 微服务两大系

阿里系:Dubbo、Zookeeper、SpringMVC\SpringBoot

Spring Cloud:Spring Cloud Netflix Eureka、SpringBoot

2、Spring Cloud是什么

  • Spring Cloud是一个开发工具集,含了多个子项目

    -利用Spring Boot的开发便利

    -主要是基于对Netflix开源组件的进一步封装

  • Spring Cloud简化了分布式开发

Redis学习(三)Java客户端Jedis

Redis的客户端有很多,比如Java客户端Jedis,Python客户端redis-py,Go客户端redigo等;我们学习的是java语言,这里我们介绍和学习java客户端Jedis。

1、什么是Jedis

Redis提供的以Java API方式使用redis的客户端,就是在java上使用redis的工具。

2、获取Jedis

Maven依赖方式:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    <type>jar</type>
    <scope>complie</scope>
</dependency>

3、Jedis直连

#1.生成一个Jedis对象,这个对象负责和指定Redis节点进行通信
Jedis jedis = new Jedis("127.0.0.1",6379);
#2.jedis执行set操作
jedis.set("hello","world");
#3.jedis执行get操作,value=“world”
String value = jedis.get("hello");

Jedis的构造函数:

Jedis(String host,int port,int connectionTimeout,int soTimeout)

host:Redis节点的所在机器的IP

port:Redis节点的端口

connectionTimeout:客户端连接超时

soTimeout:客户端读写超时

4、简单使用

1.String

//1.string
//输出结果:ok
jedis.set("hello","world");
//输出结果:world
jedis.get("hello");
//输出结果:1
jedis.incr("counter");

2.hash

//2.hash
jedis.hset("myhash","f1","v1");
jedis.hset("myhash","f2","v2");
//输出结果:{f1=v1,f2=v2}
jedis.hgetAll("myhash");

3.list

//3.list
jedis.rpush("mylist","1");
jedis.rpush("mylist","2");
jedis.rpush("mylist","3");
//输出结果:[1,2,3]
jedis.lrange("mylist",0,-1);

4.set

//4.set
jedis.sadd("myset","a");
jedis.sadd("myset","b");
jedis.sadd("myset","a");
//输出结果:[b,a]
jedis.smembers("myset");

5.zset

//5.zset
jedis.zadd("myzset",99,"tom");
jedis.zadd("myzset",66,"peter");
jedis.zadd("myzset",33,"james");
//输出结果:[[["james"],33.0],[["peter"],66.0],[["tom"],99.0]]
jedis.zrangeWithScores("myzset",0,-1);

5、Jedis连接池

Jedis直连:1.生成Jedis对象2.Jedis执行命令3.返回执行结果4.关闭Jedis连接

Jedis连接池:1.从资源池借Jedis对象2.Jedis执行命令3.返回执行结果4.归还Jedis对象给连接池

优点 缺点
直连
  • 简单方便

  • 适用于少量长期连接的场景

  • 存在每次新建/关闭TCP开销

  • 资源无法控制,存在连接泄漏的可能

  • Jedis对象线程不安全

连接池
  • Jedis预先生成,降低开销使用

  • 连接池的形式保护和控制资源的使用

相对于直连,使用相对麻烦,尤其在资源的管理上需要很多参数来保证,一旦规划不合理也会出现问题

简单使用:

//初始化Jedis连接池,通常来讲JedisPool是单例的。
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig,"ip","port");
Jedis jedis = null;
try{
    //1.从连接池获取jedis对象
    jedis = jedisPool.getResource();
    //2.执行操作
    jedis.set("hello","world");
}catch(Exeception e){
    e.printStackTrace();
}finally{
    if(jedis != null)
    //如果使用JedisPool,close操作不是关闭连接,代表归还连接池
    jedis.close();
}