Effective-Java-Chapter4

news2024/12/23 21:01:07

https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/tree/dev/Chapter-4
在这里插入图片描述

准则一 减少类和成员的可访问性

  • 如果一个方法覆盖了超类方法,那么它在子类中的访问级别就不能比超类 [JLS, 8.4.8.3] 更严格
  • 非零长度的数组总是可变的,因此对于类来说,拥有一个公共静态 final 数组字段或返回该字段的访问器是错误的。如果一个类具有这样的字段或访问器,客户端将能够修改数组的内容。这是一个常见的安全漏洞来源。
    方式一:设置不可变
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

方式二:复制一个副本

private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
    return PRIVATE_VALUES.clone();
}

注:在开发的时候对于字段的访问性要做一定思考,如果能够选择私有就尽量不暴露给外部访问。

准则二 在公共类中,使用访问器方法,而不是公共字段

如果是公共的字段,意味着客户端可以随意的修改,我们不能进行任何发辅助操作。但是如果使用get 或者 set我们可以写出下面的代码:

class Point {
    private double x;
    private double y;

    public void setX(double x) {
        if (x < 0) {
            throw new IllegalArgumentException("x can not be negative");
        }
        this.x = x;
    }
}

setX中我们可以来控制X不能小于0,但是如果直接定义成public是不安全的,我们没有办法做这个限制,同样的我们可以在get方法中进行一些辅助性操作。

准则三 减少可变性

不可变类是实例不能被修改的类。每个实例中包含的所有信息在对象的生命周期内都是固定的,因此永远不会观察到任何更改。
设计一个不可变类,请遵循以下 5 条规则:
1 不要提供修改对象状态的方法
2 确保类不能被继承。 也就是使用final修饰类。
3 所有字段用 final 修饰。
4 所有字段设为私有。
5 确保对任何可变组件的独占访问。

不可变对象本质上是线程安全的;它们不需要同步。
不可变类可以提供静态工厂(Item-1),这些工厂缓存经常请求的实例,以避免在现有实例可用时创建新实例。
你不仅可以共享不可变对象,而且可以共享它们的内部实现。例如BigInteger:

final int[] mag;
public BigInteger negate() {
   return new BigInteger(this.mag, -this.signum);
}

关于可序列化性,应该提出一个警告。如果你选择让不可变类实现 Serializable,并且该类包含一个或多个引用可变对象的字段,那么你必须提供一个显式的 readObject 或 readResolve 方法,或者使用 ObjectOutputStream.writeUnshared 或 ObjectInputStream.readUnshared 方法,即使默认的序列化形式是可以接受的。否则攻击者可能创建类的可变实例。

最佳实践
public final class String
	implements java.io.Serializable, Comparable<String>, CharSequence {
	/** The value is used for character storage. */
	private final char value[];
}

由于String 是不可变类,所以Java Lib为我们提供了StringBuilder 来操作得到不可变类String。书中也提到,如果你能够准确地预测客户端希望在不可变类上执行哪些复杂操作,那么包私有可变伴随类方法就可以很好地工作。如果不是,那么你最好的选择就是提供一个公共可变伴随类。

在这里插入图片描述

准则四 优先选择复合而不是继承

子类的功能正确与否依赖于它的超类的实现细节。超类的实现可能在版本之间发生变化,如果发生了变化,子类可能会崩溃,即使子类的代码没有被修改过。
看下面这个例子,入股我们使用继承,乍一看不会发现问题:

public class InstrumentedHashSet<E> extends HashSet<E> {
    private int addCount = 0;

    public InstrumentedHashSet() {
    }

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(java.util.Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
        s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
        System.out.println("s.addCount = " + s.addCount);
    }
}

让我们看看运行结果:
在这里插入图片描述
明明我们只是添加了3个元素,为什么会有6个元素了,那是因为我们的实现依赖于父类,但是父类的实现我们看看addAll是怎样实现的呢?
在这里插入图片描述
看到这里获取明白了,在添加单个元素的时候数量又被加了一遍,当然我们可能不会犯这种错误,这个例子只是为了说明我们继承一个来,依赖于父类的实现的时候,可能会疏忽导致程序出现错误,这种错误往往排查起来比较困难。如果我们换一种实现方式:

 public class ForwardingSet<E> implements Set<E> {
        private final Set<E> s;

        public ForwardingSet(Set<E> s) {
            this.s = s;
        }

        public void clear() {
            s.clear();
        }

        public boolean contains(Object o) {
            return s.contains(o);
        }

        public boolean isEmpty() {
            return s.isEmpty();
        }

        public int size() {
            return s.size();
        }

        @Override
        public Iterator<E> iterator() {
            return s.iterator();
        }

        public boolean add(E e) {
            return s.add(e);
        }

        public boolean remove(Object o) {
            return s.remove(o);
        }

        public boolean containsAll(Collection<?> c) {
            return s.containsAll(c);
        }

        public boolean addAll(Collection<? extends E> c) {
            return s.addAll(c);
        }

        public boolean removeAll(Collection<?> c) {
            return s.removeAll(c);
        }

        public boolean retainAll(Collection<?> c) {
            return s.retainAll(c);
        }

        public Object[] toArray() {
            return s.toArray();
        }

        public <T> T[] toArray(T[] a) {
            return s.toArray(a);
        }

        @Override
        public boolean equals(Object o) {
            return s.equals(o);
        }

        @Override
        public int hashCode() {
            return s.hashCode();
        }
        @Override
        public String toString() {
            return s.toString();
        }
    }

这样实现有两个优点:

  • 首先可以避免前面提到的问题。
  • 其次我们会发现Set是接口,接口往往是比较稳定的,但是具体实现可能随着版本的更新有所变化,这样我们不用依赖于具体实现,我们可以随时更改,灵活性增强了

这就是聚合带来的好处,它的灵活性更强,且不严重依赖于某个具体的类

只有子类确实是超类的子类型的情况下,继承才合适。换句话说,两个类 A、B 之间只有 B 满足「is-a」关系时才应该扩展 A。如果你想让 B 扩展 A,那就问问自己:每个 B 都是 A 吗?如果不能对这个问题给出肯定回答,B 不应该扩展 A;如果答案是否定的,通常情况下,B 应该包含 A 的私有实例并暴露不同的 API:A 不是 B 的基本组成部分,而仅仅是其实现的一个细节。

准则五 继承要设计良好并且具有文档,否则禁止使用

好的 API 文档应该描述一个给定的方法做什么,而不是如何做?是的,它确实违背了!这是继承违反封装这一事实的不幸结果。要为一个类编制文档,使其能够安全地子类化,你必须描述实现细节,否则这些细节应该是未指定的。

构造函数不能直接或间接调用可重写的方法,这个是什么意思呢?
public static final class Sub extends Super {
      // Blank final, set by constructor
      private final Instant instant;
      Sub() {
          instant = Instant.now();
      }
      // Overriding method invoked by superclass constructor
      @Override
      public void overrideMe() {
          System.out.println(instant);
      }
  }
  public static class Super {
      // Broken - constructor invokes an overridable method
      public Super() {
          overrideMe();
      }
      public void overrideMe() {
      }
  }

在这里插入图片描述
这个例子就可以看到如果在超类构造函数调用了重写的方法付可能会发生错误,例如我们这个时候如果在后面继续使用instant这个对象,那么将会产生空指针错误,原因在于在调用父类构造函数的时候,这个变量还没有进行初始化。
因此如果一个具体的类没有实现一个标准的接口,那么你可能会因为禁止继承而给一些程序员带来不便。如果你认为必须允许继承此类类,那么一种合理的方法是确保该类永远不会调用其任何可重写的方法

Cloneable 和 Serializable 在继承中会带来什么问题?

问题:如果一个类实现了 Cloneable 接口,那么它的所有子类都必须小心处理 clone 方法,以确保正确地复制对象的状态。
解决方案:
使类本身不实现 Cloneable 接口,而是提供一个受保护的 clone 方法,并在其中调用 super.clone()。
如果子类需要支持克隆功能,可以显式地覆盖这个受保护的方法。
Item-86: 使类可序列化
问题:如果一个类实现了 Serializable 接口,那么它的所有子类都必须考虑序列化的问题,特别是如何处理瞬态字段和版本控制等问题。
解决方案:
类本身不实现 Serializable 接口,而是通过提供一个受保护的序列化方法或使用默认的序列化机制来实现序列化。
子类可以选择性地覆盖序列化相关的方法或者添加序列化所需的逻辑。

准则六 接口优于抽象类

1、抽象类的局限:一个类要实现抽象类定义的类型,该类必须是抽象类的子类。因为 Java 只允许单一继承,这种限制对抽象类而言严重制约了它们作为类型定义的使用。
2、接口的优点:任何定义了所有必需的方法并遵守通用约定的类都允许实现接口,而不管该类驻留在类层次结构中何处。

接口的好处
  • 接口是定义 mixin(混合类型)的理想工具。
    这是什么意思呢?例如Comparable 这个接口,如果我们想要对对象进行比较排序只需要实现这个接口就可以拥有这个能力,但是如果是抽象类就不是特别方便来提供一个抽象方法用于提供这个能力,首先如果是这样所有的子类必须继承这个抽象类,本来Java是单继承这样做显然不太合适,如果这个类以后还要继承别的类怎么办呢?再就是如果有些类和这个抽象类不存在父子关系,从这个角度上来说也不太合适。
  • 接口允许构造非层次化类型框架。
public interface Singer {
    AudioClip sing(Song s);
}

public interface Songwriter {
    Song compose(int chartPosition);
}

public interface SingerSongwriter extends Singer, Songwriter {
    AudioClip strum();
    void actSensitive();
}

这两个接口一个是歌唱者,一个是作词者,如果一个人是两个身份,使用接口是不是很好的整合了这两种能力呢?这里要提一下,之所以这里这么好整合,是因为我们的接口隔离 + 单一职责,我们在整合的时候不会冲突。

接口使用的最佳方式
  • 如果接口方法的实现与其他接口方法类似,那么可以考虑以默认方法的形式为程序员提供实现帮助。我们来看一个例子来帮助理解:
public interface Shape {

     // 计算面积
     default double area(BiFunction<Double, Double, Double> formula, Double... args) {
         if (formula == null) throw new IllegalArgumentException("Formula cannot be null");
         return formula.apply(args[0], args[1]);
     }

     // 计算周长
     default double perimeter(BiFunction<Double, Double, Double> formula, Double... args) {
         if (formula == null) throw new IllegalArgumentException("Formula cannot be null");
         return formula.apply(args[0], args[1]) * 2;
     }
 }

 public class Rectangle implements Shape {

     // 计算矩形面积
     public double calculateArea(double length, double width) {
         return area((l, w) -> l * w, length, width);
     }

     // 计算矩形周长
     public double calculatePerimeter(double length, double width) {
         return perimeter((l, w) -> l + w, length, width);
     }
 }

补充一下BiFunction这个函数式接口:
在这里插入图片描述

  • 你可以通过提供一个抽象骨架实现类来结合接口和抽象类的优点。接口定义了类型,可能提供了一些默认方法,而骨架实现类在基本接口方法之上实现了其余的非基本接口方法。扩展骨架实现需要完成实现接口的大部分工作。
    这个比较好举例子,例如我们Jdk源码中的AbstractCollection
    在这里插入图片描述
    我们可以看到Collection中有一个默认的方法,然后AbstractCollection拓展实现了大部分方法,这也是模板设计模式的应用。
    在这里插入图片描述
    在实践的过程中,我们先分析那些事基本方法需要交给子类来实现,在抽象类中的实现可以是一个抽象方法,如果是通用的默认实现则可以在抽象类中直接实现具体的逻辑。例如源码中的AbstractMapEntry:
abstract class AbstractMapEntry<K, V> implements Entry<K, V> {

  @Override
  public abstract K getKey();

  @Override
  public abstract V getValue();

  @Override
  public V setValue(V value) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean equals(@NullableDecl Object object) {
    if (object instanceof Entry) {
      Entry<?, ?> that = (Entry<?, ?>) object;
      return Objects.equal(this.getKey(), that.getKey())
          && Objects.equal(this.getValue(), that.getValue());
    }
    return false;
  }

  @Override
  public int hashCode() {
    K k = getKey();
    V v = getValue();
    return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode());
  }

  /** Returns a string representation of the form {@code {key}={value}}. */
  @Override
  public String toString() {
    return getKey() + "=" + getValue();
  }
}

准则七 为后代设计接口

这个准则告诉我们,向接口中添加默认方法也是存在风险的。书中举例子Collection#removeIf,这个方法是非常通用的,采用迭代器实现,单线程的情况下,不会出现ConcurrentModificationException 如果是多线程情况下就需要加锁。可以看到JDK中的SynchronizedCollection从写了这个方法,如果不复写用了默认实现是存在风险的,这就是这个准则想要表达的意思。

public boolean removeIf(Predicate<? super E> filter) {
    synchronized (mutex) {return c.removeIf(filter);}
}

这里补充一下ConcurrentModificationException 为什么会出现:
在于我们直接调用List的add方法和remove 方法会修改modCount, 这里判断不会通过就会抛出异常。所以实践中一般推荐使用迭代器进行删除操作,也不推荐在遍历集合的时候对集合进行修改操作,如果要进行修改操作,可以先复制一份集合然后进行操作。可以使用System.arraycopy方法。

final void checkForComodification() {
   if (modCount != expectedModCount)
       throw new ConcurrentModificationException();
}

准则八 接口只用于定义类型

当一个类实现了一个接口时,这个接口作为一种类型,可以用来引用类的实例。因此,实现接口的类应该说明使用者可以对类的实例做什么。为其他任何目的定义接口都是不合适的。
在实践中我们往往会将一些常量放到接口中,但是书中作者似乎更推荐我们把常量放到类中。

准则九 类层次结构优于带标签的类

你可能会遇到这样一个类,它的实例有两种或两种以上的样式,并且包含一个标签字段来表示实例的样式。这个准则直接看例子比较容易理解:

class Figure {
    enum Shape {RECTANGLE, CIRCLE};

    // Tag field - the shape of this figure
    final Shape shape;

    // These fields are used only if shape is RECTANGLE
    double length;

    double width;

    // This field is used only if shape is CIRCLE
    double radius;

    // Constructor for circle
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }

    // Constructor for rectangle
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    double area() {
        switch (shape) {
            case RECTANGLE:
                return length * width;
            case CIRCLE:
                return Math.PI * (radius * radius);
            default:
                throw new AssertionError(shape);
        }
    }
}
// Class hierarchy replacement for a tagged class
abstract class Figure {
    abstract double area();
}

class Circle extends Figure {
    final double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * (radius * radius);
    }
}

class Rectangle extends Figure {
    final double length;
    final double width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    double area() {
        return length * width;
    }
}

准则十 静态成员类优于非静态成员类

  • 静态成员类是最简单的嵌套类。最好把它看做是一个普通的类,只是碰巧在另一个类中声明而已,并且可以访问外部类的所有成员,甚至那些声明为 private 的成员。静态成员类是其外部类的静态成员,并且遵守与其他静态成员相同的可访问性规则。如果声明为私有,则只能在外部类中访问,等等。

  • 静态成员类的一个常见用法是作为公有的辅助类,只有与它的外部类一起使用时才有意义。这句话是什么意思呢?比如我们看下面的例子,Operation 就是一个辅助类,辅助计算机完成运算。

public class Calculator {
// Static member class for operations
public static enum Operation {
    PLUS("+") {
        @Override
        double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        @Override
        double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        @Override
        double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        @Override
        double apply(double x, double y) {
            if (y == 0) throw new ArithmeticException("Cannot divide by zero");
            return x / y;
        }
    };

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    abstract double apply(double x, double y);

    @Override
    public String toString() {
        return symbol;
    }
}

非静态成员类的每个实例都隐式地与外部类的外部实例相关联。在非静态成员类的实例方法中,你可以调用外部实例上的方法,或者使用受限制的 this 构造获得对外部实例的引用 [JLS, 15.8.4]。如果嵌套类的实例可以独立于外部类的实例存在,那么嵌套类必须是静态成员类:如果没有外部实例,就不可能创建非静态成员类的实例。
还是来看一个例子:

public class ExternalClass {
    private final int value = 10;

    public NonStaticMemberClass createNonStaticMember() {
        return new NonStaticMemberClass();
    }

    public class NonStaticMemberClass {
        public void printValue() {
            // 可以访问外部类的私有成员
            System.out.println(value);
        }
    }
}

我们通过反编译可以看到,我们访问外部变量的时候其实是持有了外部类的实例。
在这里插入图片描述
那如果内部内不需要访问内部的成员,或者该类可以独立的存在,一般都要定义为static class
如果声明的成员类不需要访问外部的实例,那么应始终在声明中添加 static 修饰符,使其成为静态的而不是非静态的成员类。比如Java中的Node和Entry都是静态的,他们并不需要持有外面的Map对象,Map对象中的数组来引用Node.
在这里插入图片描述
非静态成员类的一个常见用法是定义一个 Adapter [Gamma95],它允许外部类的实例被视为某个不相关类的实例。它的使用场景在源码中也可以看到,比如我们的List的Itr 我们需要引用外部的实例变量,但是这个实现我们在类的外部不需要访问,所以可以使用内部类。
在这里插入图片描述

  • 匿名类除了在声明它们的时候,你不能实例化它们。你不能执行 instanceof 测试,也不能执行任何其他需要命名类的操作。你不能声明一个匿名类来实现多个接口或扩展一个类并同时实现一个接口。匿名类的使用者除了从超类继承的成员外,不能调用任何成员。因为匿名类出现在表达式中,所以它们必须保持简短——大约 10 行或更短,否则会影响可读性。现在的实践一般都是通过lambda表达式来实现。

准则十一 源文件仅限有单个顶层类

这个准则很简单,就是在一个源文件中只有一个顶层的类。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1992416.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3、pnpm yarn npm

项目里实际上就只有这些依赖 node module 里却有很多的包 原因&#xff1a; 比如说vue&#xff0c;vue内部有依赖了其余的包。工具又依赖了别的依赖 造成的问题&#xff1a;我可以直接去用这个包&#xff0c;但是这个包在package.json中却没有看到-----幽灵依赖 那如果说别…

Python 绘图入门

数据可视化的概念及意义 数据可视化有着久远的历史&#xff0c;最早可以追溯至10世纪&#xff0c;至今已经应用和发展了数百年。不知名的天文学家是已知的最早尝试以图形方式显示全年当中太阳&#xff0c;月亮和行星的位置变化的图。 图1 数据可视化的发展历程 什么是数据可视…

就医陪诊小程序项目开发功能介绍

陪诊小程序通常是指一种通过智能手机应用程序提供陪同就医服务的平台。其主要功能可以包括 预约挂号服务&#xff1a; 用户可以通过小程序预约医院或特定科室的就诊时间&#xff0c;避免排队等待。 陪同就医&#xff1a; 提供专业的陪诊员工作&#xff0c;陪同用户到医院就诊&…

如何理解 Java 中的阻塞队列:从基础到高级的深度解析

提到阻塞队列&#xff0c;许多人脑海中会浮现出 BlockingQueue、ArrayBlockingQueue、LinkedBlockingQueue 和 SynchronousQueue。尽管这些实现看起来复杂&#xff0c;实际上阻塞队列本身的概念相对简单&#xff0c;真正挑战在于内部的 AQS&#xff08;Abstract Queuing Synchr…

javaweb_04:SpringBoot

一、SpringBoot快速入门 官网&#xff1a;https://spring.io/ spring提供了若干个子项目&#xff0c;每个项目用于完成特定的任务。 1、创建springboot工程&#xff0c;并勾选web开发相关依赖。 注意这里type要选成maven: 2、定义helloController类&#xff0c;添加方法h…

QT多语言工具实现支持生成ts文件,ts文件和xlsx文件互转

一. 工具介绍 1.如果你是Qt项目,为多语言发愁的话,看到这篇文件,恭喜你有福啦!工具截图如下:​ 2.在项目开发的过程中,尽量将所有需要翻译的文本放在一个文件中,qml翻译用一个文件,cpp用一个,如下: test.h #pragma once /******************************************…

Python:jupyter 模型可视化(VS)

step1:打开vs安装扩展 安装后重新启动vs 建立可视化模型 import pandas as pd from sklearn.tree import DecisionTreeClassifier from sklearn import treemusic_data pd.read_csv(music.csv)Xmusic_data.drop(columns[genre]) Ymusic_data[genre]modelDecisionTreeClassifie…

吴恩达机器学习 笔记四十 寻找相关特征 协同过滤的限制

寻找相关特征&#xff1a; 要找到其他和 x(i) 相关的项&#xff0c;即找到一个 item k&#xff0c; x(k) 与 x(i) 相似。x 是一个向量&#xff0c;判断相似用的是下图中的式子 &#xff0c;即 x(k) 和 x(i) 之间的平方距离&#xff0c;有时也写成下面那种形式。 协同过滤的缺点…

openfoam中为什么一个单元用27个点表示,代表什么图形(由27个节点组成的三维立方体单元,在有限元方法(FEM)中被称为“三次立方体单元”)

问题: 近期在做openfoam项目的时候,发现openfoam中固体的点为什么一个单元用27个点表示,想着代表什么图形呢?如果以顶点表示的话好像图形就复杂了,然后查询一下资料,结果如下 解答: 在OpenFOAM中,使用27个点来表示一个单元通常指的是一种高阶单元。这种单元类型在有…

PSINS工具箱|天文导航cns和卫星导航gps的对比|MATLAB源代码

文章目录 介绍运行结果CNS观测的姿态曲线滤波后的状态曲线轨迹曲线对比三轴位置曲线误差CDF(累计概率密度函数)图像函数源码介绍 天文导航(cns)+ins组合导航和gps+ins导航的结果对比,MATLAB的源代码,基于psins工具箱。 工具箱介绍:PSINS工具箱是一个开源的惯性导航系统…

odoo17 搜索栏升级的真是太方便了

odoo&#xff11;&#xff17; 搜索栏升级的真是太方便了 几行代码&#xff0c;惊人效果 代码&#xff1a; <!-- 搜索--><record model"ir.ui.view" id"bzglsp.jiancexm_search"><field name"name">搜索</field><…

文件销毁,硬盘销毁,数据销毁,巴黎奥运会:一场GDPR大考,硬盘文件数据销毁

巴黎奥运会在使用智能设备和系统的情况下&#xff0c;如何满足欧盟严格的数据保护要求&#xff1f; 2024年夏季&#xff0c;巴黎迎来备受瞩目的奥运盛会&#xff0c;预计将吸引上百万游客到访。为保障这一全球性体育盛会的顺利进行&#xff0c;法国政府启用了一系列智能系统和…

探索IT服务台自动化的办法

如今&#xff0c;IT 服务管理 (ITSM) 工具已经有了内置智能的自动化功能。人工智能 (AI) 和机器学习 (ML) 可以自动提供更好的服务&#xff0c;比如给出基于上下文的建议、进行异常检测、做根本原因分析等等。而且&#xff0c;AI 还可以和物联网 (IoT)、机器人流程自动化 (RPA)…

阿里云SSL证书 部署Windows服务器

实现将阿里云SSL证书部署到Windows IIS 服务器中&#xff0c;方便https请求 第一步、获取并下载SSL证书 1.购买证书&#xff08;一年20个&#xff09;&#xff0c;如果没有SSL证书就需要去购买个人测试证书&#xff0c;有效期3个月 2.创建证书 3.下载证书 第二步、安装证书 …

大数据-70 Kafka 高级特性 物理存储 日志存储 日志清理: 日志删除与日志压缩

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

万字长文讲透数字化转型

温馨提醒&#xff1a;1.6w字详细拆解&#xff0c;内容篇幅较长&#xff0c;建议先收藏~ 数字化浪潮正在席卷全球&#xff0c;践行数字化转型和提升企业的运营水平与竞争力&#xff0c;已经成为各国企业角力全球市场的重要议题。为此&#xff0c;很多国家政府都推出了鼓励和推动…

开发者们都在讨论Bandizip,你真的不心动吗?

前言 在这个信息爆炸的时代&#xff0c;数据如潮水般涌来&#xff0c;我们的电脑空间似乎永远不够用&#xff1b;每当面对堆积如山的文件&#xff0c;你是否也曾感到头疼不已&#xff1f;别急&#xff0c;小江湖今天就要带你走进一个神奇的世界&#xff0c;那里有一款软件&…

Linux:多线程(二.理解pthread_t、线程互斥与同步、基于阻塞队列的生产消费模型)

上次讲解了多线程第一部分&#xff1a;Linux&#xff1a;多线程&#xff08;一.Linux线程概念、线程控制——创建、等待、退出、分离&#xff0c;封装一下线程&#xff09; 文章目录 1.理解Linux下线程——理解tid2. Linux线程互斥2.1相关概念2.2引入问题分析问题解决思路 2.3L…

Sqli-labs靶场65关详解(一)

前言:目的是通过构造sql语句来窃取数据库数据 一.sqli-labs靶场(1~4) 1~4使用了union联合查询字符型注入,要点在于闭合单双引号括号 要点:union联合查询 UNION 操作符用于合并两个或多个 SELECT 语句的结果集UNION 内部的 SELECT语句必须拥有相同数量的列列也必须拥有相似的…

YARN 的介绍

YARN 的介绍 一、YARN 产生背景1.1 MapReduce 1.0 系统架构 1.2 MapReduce 1.0架构缺陷二、YARN 是什么三、YARN 作用四、YARN 架构五、工作原理六、MapReduce ON YARN 工作流程七、YARN 的容错性八、YARN 的高可用八、YARN 调度器8.1 先进先出调度器8.2 容量调度器8.3 公平调度…