首页 文章 精选 留言 我的

精选列表

搜索[深度集成],共10008篇文章
优秀的个人博客,低调大师

java泛型深度解读

简介 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型 ( type parameters ),也就是说所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法的创建中. 在泛型类中定义参数化类型,在泛型表达式中,需要指定具体类型,即泛型在使用过程中将会被替换为具体的类型. // 定义 参数类型 class ArrayList<E> // 使用中 指定具体类型 ArrayList<String> list = new ArrayList<>(); 原始类型(raw type) : 就是去掉参数类型后的类,如示例中的ArrayList. 为什么需要泛型 我们来看一个例子: List list = new ArrayList(); // 下列的添加方法完全没问题 list.add("one"); list.add(1); // 取的时候, 如果你小心的,也没问题 // 需要强转, 内部是以Object引用来存放 String s = (String) list.get(0); int i = (int) list.get(1); // 但是如果, 不小心在获取时 类型判断出错的话 for (int index = 0; index < list.size(); index++) { String str = (String) list.get(index); // index = 1时, 抛出java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String } 上诉方式,有两个问题. 由于java是静态语言,应该尽量避免在一个容器数组中,添加不相干的类型实例.否则可能引起类型转换错误. 这种方式,没有类型检查,只能够在运行时候,系统抛出异常后,你才会发现错误. 接下来使用泛型: List<Animal> list = new ArrayList<>(); // 可以添加Animal及其子类 list.add(new Animal()); list.add(new Tiger()); // 编译器进行类型检査,避免插人错误类型的对象 // 编译时期报错, list.add("one"); 可以看出,泛型只允许添加 声明的类及其子类,其他无关类无法加入到list中,并且尝试将其他类型加入列表,将在编译时直接报错. 由此可以看出泛型的特点: 能够对类型进行限定 在编译期对类型进行检查,编译时报错 对于获取明确的限定类型,无需进行强制类型转化 具有良好的可读性和安全性 泛型类 一个泛型类 ( generic class ) 就是具有一个或多个类型变量的类.定义的变量用尖括号 <> 括起来,放在类名的后面. public class Holder<T> { private T obj; public Holder(T t) { obj = t; } public void put(T t) { obj = t; } public T get() { return obj; } } 泛型定义的类型变量,可以在 成员变量, 方法参数, 局部变量, 方法返回值中使用.要注意的是,静态变量和静态方法中,不能使用类中定义的泛型参数. 这里的 T 可以代表任意类型(Object或其子类),需要注意的是,基本数据类型不能够使用泛型,需要使用它们对应的包装类(wrapper Class) 用具体的类型替换类型变量就可以实例化泛型类型,如 Holder<String> holder = new Holder<>(); 泛型接口 泛型接口与泛型类对比区别别是,泛型接口中不能使用 类型参数作为成员变量. 泛型类的继承 当父类为泛型类或者接口时,子类可以使用具体类型来继承父类,也可以使用类型参数继承父类 public interface Parent<T> { ... // ====== // 使用具体类型来继承父类 public class Son implements Parent<Animal> { ... // ====== // 使用类型参数继承父类 public class Son<E> implements Parent<E> { ... 但是要注意, 一个类不能实现同一个泛型接口的两种变体,由于类型擦除的原因,这两个变体会成为县宫廷的接口 // Error public class Son implements Parent<Animal> { ... // ========= Error public class Child extends Son implements Parent<String> { ... 这种方式,Child是实现了Parent<Animal>和Parent<String>,是不允许的. 泛型方法 除了泛型类,还可以声明一个泛型方法. 泛型方法可以在泛型类中声明,也可以在普通方法中声明. 注意的是,静态方法中,只能使用方法中定义的类型参数,而不能使用泛型类中的类型参数. // 普通类中的泛型方法 public class Normal { // 成员泛型方法 public <E> String getString(E e) { return e.toString(); } // 静态泛型方法 public static <V> void printString(V v) { System.out.println(v.toString()); } } // 泛型类中的泛型方法 public class Generics<T> { // 成员泛型方法 public <E> String getString(E e) { return e.toString(); } // 静态泛型方法 public static <V> void printString(V v) { System.out.println(v.toString()); } } 一个原则:在能达到目的的情况下,尽量使用泛型方法。即,如果使用泛型方法可以取代将整个类泛化,那么应该有限采用泛型方法。 泛型类中的参数类型和泛型方法中的参数类型,即使声明为相同的类型参数,如T,两者的类型不会相互影响,甚至可以说没有任何关联.方法中的类型,由传入的参数决定,与泛型类的类型无关. // 泛型类中的类型参数 用 T 表示 public class Holder<T> { ... // 成员泛型方法, 声明的类型参数,也用 T 表示 public <T> String getString(T t) { return t.toString(); } } public static void main(String[] args) { // 泛型类为 Animal Holder<Animal> holder = new Holder<>(new Animal()); // 泛型方法为 Vegetation String s = holder.getString(new Vegetation()); System.out.println(s); // I'm Vegetation } 泛型方法的使用过程中,无需对类型进行声明,它可以根据传入的参数,自动判断. public class Main { public static void main(String[] args) { // 指定类型 Main.<String>printString("one"); // 不指定,自动推倒 Main.printString("two"); } static <T> void printString(T t) { System.out.println(t.toString()); } } 类型变量的限定 对于类型变量没有限定的泛型类或方法, 它是默认继承自Object,当没有传入具体类型时,它有的能力只有Object类中的几个默认方法实现. 如果我们要实现一个方法, 传入两个参数,返回其中大的一个,即max()函数. public static void main(String[] args) { // 传入 4 , 2 , 自动装箱成Integer类 int r = max(4, 2); } static <T> T max(T t1,T t2){ // Cannot resolve method 'compareTo(T)' return t1.compareTo(t2) > 0 ? t1 : t2; } 如果没有对类型进行限定,它默认只有Object能力,它没有compareTo方法,因此没有比较能力,此时,即使在调用的时候传入可以比较的对象, max方法会在编译器报错. 此时, 我们就需要对 类型参数进行限定,让它能够默认拥有一些类的"能力". public static void main(String[] args) { // 传入 4 , 2 , 自动装箱成Integer类 int r = max(4, 2); // r = 4 } // 继承 Comparable 的类具有比较功能,能够比较大小 , 该函数返回传入的最大值 static <T extends Comparable<T>> T max(T t1, T t2) { return t1.compareTo(t2) > 0 ? t1 : t2; } 从代码中可以看出, T 被限定为Comparable的子类(Comparable类本身是泛型类,也需要对他进行类型参数声明,否则会引发编译警告.),因此它拥有了 父类Comparable有的能力,即比较功能,这样我们才能得到正确的结果. 类型参数的限定 可以记为 <T extends BoundingType>,由于java有单继承类多实现接口的特点,因此还可以有多个限定. <T extends BoundingType1 & BoundingType2 & ...> 在 Java 的继承中, 可以拥有多个接口超类型, 但限定中至多有一个类。 如果用 一个类作为限定, 它必须是限定列表中的第一个. 泛型的实现原理 java中泛型的实现是采用 类型擦除 的方式实现. 所谓的类型擦除,就是程序在编译阶段,编译器会对泛型变量进行擦除(erased)操作,并替换为限定类型 (没有限定的变量用 Object);泛型类也将擦除为原始类型; 在泛型表达式中(泛型的使用),会将类型替换为具体的类型(此时将发生强制转换) 下面我们通过,反编译泛型类的方式来揭开类型擦除的面纱. 使用jad -sjava Holder.class来反编译Holder类. // 泛型类 public class Holder<T> { private T obj; public void put(T t) { obj = t; } public T get() { return obj; } } // 泛型使用 public static void main(String[]args){ Holder<Animal> holder=new Holder<>(); holder.put(new Tiger()); // 在使用过程中没有发生强转 Animal animal=holder.get(); } // --------------------------------- // 反编译出来的类, 它的类型被擦除为Object public class Holder { private Object obj; public Holder() { } public void put(Object t) { obj = t; } public Object get() { return obj; } } public static void main(String args[]){ Holder holder=new Holder(); holder.put(new Tiger()); // 泛型的使用过程中,使用强制转换为目标类型 Animal animal=(Animal)holder.get(); } // ------------------------------------- // javap 反汇编对main方法,到处的指令码 public static void main(java.lang.String[]); Code: 0:new #2 // class generics/Holder 3:dup 4:invokespecial #3 // Method generics/Holder."<init>":()V 7:astore_1 8:aload_1 9:new #4 // class bean/Tiger 12:dup 13:invokespecial #5 // Method bean/Tiger."<init>":()V 16:invokevirtual #6 // Method generics/Holder.put:(Ljava/lang/Object;)V 19:aload_1 20:invokevirtual #7 // Method generics/Holder.get:()Ljava/lang/Object; 23:checkcast #8 // class bean/Animal 26:astore_2 27:return 由上述反编译的代码可以看出,泛型类被擦除为 原始类型; 泛型类中的类型参数变量也擦除为Object类型; 泛型的表达式中,发生了强制转换为目标类型.从反汇编代码中可以看出,holder.get()方法,被分解为两条指令.1. 原始类型调用方法,对应 invokevirtual指令2. Object类型强制转换为Animal类型,对应 checkcast指令 如果限定为 T extends Animal,则类型参数变量将被擦除为限定类型Animal. // 泛型类 public class Holder<T extends Animal> { private T obj; ... // 反编译类 public class Holder { private Animal obj; ... java泛型的局限 不能用基本类型实例化类型参数 泛型中的 类型参数在没有限定的情况下 是默认 擦除为 Object,而基本类型变量无法转化为 Object类型. 不过没关系, java中8中基本类型,都有其对应的包装类(Wrapper Class), 并且基本类型 使用参数传递是,将被 (自动装箱)AutoBoxing 为包装类型. 运行时,无法对类型参数进行检查 由于编译时,擦除了类型参数, 因此,所有的类型查询只产生原始类型. 因此,一下的语句是不可行的. Holder<Tiger> holder = new Holder<>(new Tiger()); // ERROR 无法对类型参数进行判断 if (holder instanceof Holder<Tiger>) ; if (holder instanceof Holder<T>) ; 由于类型擦除,Holder<String>和Holder<Animal>的实例,获取的类都是原始类,是一样的,所以他们的getClass()方法的返回是一样的. 不能直接创建参数化类型的数组 如这样的代码Holder<Animal>[] holders = new Holder<Animal>[2],是通过不了编译的. 但是,可以通过以下方式来创建数组,不会报错,只是受到警告 // 使用原始类型而后强制转换 Holder<Animal>[] holders = (Holder<Animal>[]) new Holder[2]; // 使用通配符而后强制转换 Holder<Animal>[] holders = (Holder<Animal>[]) new Holder<?>[2]; 不能够实例化类型变量 这样的语句T t = new T()和T t = new Object(),是通过不了编译的,一定要在泛型表达式中申明了具体类型,才能创建. 某些情况下,我们需要创建 参数类型的变量, 那么前提是一定要知道被创建的类型.可以通过以下两种方式来创建: 反射创建 // 使用(如) newObject(Animal.class); static <T> T newObject(Class<T> cls) throw Exception{ return cls.newInstance(); } jdk8以后,可使用构造器表达式 // 使用(如) newObject(Animal::new); static <T> T newObject(Supplier<T> constr) { return constr.get(); } 不能构造泛型数组 不能直接实例化 类型数组,如T[] arr = new T[2]. 但是可以这样 T[] arr = new Object[2], 原因是数组本身也有类型,用来监控存储在虚拟机 中的数组,这个类型会被擦除为Object. 虽然这种方式能够创建泛型数组,但是为了类型安全起见,最好提供构造器来实现泛型数据的创建. // 使用构造器 // 使用(如) newArray(Animal[]::new, 2); static <T> T[] newArray(IntFunction<T[]> constr, int length) { return constr.apply(length); } // 使用反射 // 使用(如) newArray(String.class, 2); static <T> T[] newArray(Class<T> cls, int length) throws Exception { return (T[]) Array.newInstance(cls, length); } 不能在静态变量和静态方法中,使用泛型类中的类型参数 如以下的方式都不允许 public class Test<T> { // Error private static T t; // Error public static T test() { T t; } } 注意擦除后的冲突 由于类型擦除,方法重写时,会下列冲突. public class Holder<T> { public boolean equals(T t) { ... } 由于类型擦除,该方法会被擦除为boolean equals(Object t),这和Object类中的equals方法完全冲突了,返回值和方法名,参数都一致了. 这种冲突,解决方案只能是,将方法重命名! 由于类型擦除,方法重载时,也可能发生冲突. 再观察下面的代码: public interface Parent<T> { T get(); } public class Son implements Parent<Animal> { @Override public Animal get() { return new Animal(); } } public class Main { public static void main(String[] args) { Parent<Animal> parent = new Son(); Animal animal = parent.get(); } } 父类中根据类型擦除, 拥有Object get()方法, 子类传入具体的类型参数,拥有Animal get()方法,且继承父类方法,所以子类中同时拥有这两个方法. 这两个方法,方法参数相同,就只有返回值类型不一样. 在java语法中,是不允许这样的两个方法同时在一个类中存在,会把他们认为是同一种方法(方法签名根据方法参数和方法名来确定). 但是jvm却能分辨,jvm的方法签名,是通过方法参数,方法返回值,方法名来确定的,所以jvm允许这样的方法存在, 因此,对于这种冲突,不需要我们自己来处理, jvm通过一种称为 Bridge Method的方式来实现这种方式下的多态调用冲突. 感兴趣的可以查看,笔者的另一篇文章java中多态的实现原理. 为什么使用 类型擦除来实现泛型 因为,泛型提出来时,已经是java1.5的版本,java已经经历过10年的发展,java遗留的代码量可想而知. 为了兼容这部分的(旧)代码,而不得不采用这种方式来实现. 通配符 <?> <? extends T> <? super T> 泛型通配符解释起来比较复杂,这里就不进行展开,感兴趣的可以查看笔者的另一篇文章java泛型通配符详解及实践 引用 java核心技术 卷1(第10版)

优秀的个人博客,低调大师

深度解析volatile—底层实现

我们都知道,Java关键字volatile的作用 1、内存可见性 2、禁止指令重排序 可见性是指,在多线程环境,共享变量的操作对于每个线程来说,都是内存可见的,也就是每个线程获取的volatile变量都是最新值;并且每个线程对volatile变量的修改,都直接刷新到主存。 下面重点介绍指令重排序。 为什么要指令重排序? 为了提高程序执行的性能,编译器和执行器(处理器)通常会对指令做一些优化(重排序) 1、编译器重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序; 2、处理器重排序。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序; 学过《编译原理》同学应该知道,现代高级编程语言的编译器,实现都很复杂。 编译器基本构造包括:语法分析、词法分析、语义分析、中间代码生成、指令优化、目标代码产生。 第一阶段:编译器优化,就是发生在编译阶段,就Java而言,就是java源码编译生成class字节码的时候,对编译生成的中间代码进行的一次指令优化。Java的编译器是javac.exe。 第二阶段:执行器(处理器)优化,和不同的处理器硬件厂商的实现有关,也和Java的执行器(java.exe,也称Java解释器)有关。执行器优化,是对于机器指令在目标平台的机器上运行,做的一层优化。 我们知道,现代高级编程语言,经过编译后,产生目标代码,如.java的源文件编译后生成.class字节码文件,.cpp源文件经过C++编译器编译后生成.o对象文件。 这些编译后生成的文件,不能直接在机器上运行,而是需要转化成特定平台的机器指令。机器能够运行的指令,是需要这个平台、这个机器能正确识别的。 相同的一份源码,最终转化成不同平台上的机器指令,是不同的。 这也更容易理解:汇编指令,并不是跨平台的。Windows下通常使用Intel汇编,而Linux下多用AT&T汇编,它们在语法上存在差异,运行效果也依赖于各自平台的实现。 在Java中,为了提高运行效率,javac编译器,和java解释器,在2个阶段分别对指令进行了优化,也就是重排序。 Java重排序的前提:在不影响 单线程运行结果的前提下进行重排序。也就是在单线程环境运行,重排序后的结果和重排序之前按代码顺序运行的结果相同。 指令重排序对单线程没有什么影响,它不会影响程序的运行结果,但是会影响多线程的正确性。 Java因为指令重排序,优化我们的代码,让程序运行更快,也随之带来了多线程下,指令执行顺序的不可控。既然指令重排序会影响到多线程执行的正确性,那么我们就需要某些情景下禁止重排序。Java提供给我们禁止重排序能力的操作——就是volatile。 那么JVM的volatile是如何禁止重排序的呢? 在具体探究之前,我们先看另一个原则happens-before,happen-before原则保证了程序的“有序性”,它规定如果两个操作的执行顺序无法从happens-before原则中推到出来,那么他们就不能保证有序性,可以随意进行重排序。其定义如下: 1、同一个线程中的,前面的操作 happen-before 后续的操作。(即单线程内按代码顺序执行。但是,在不影响在单线程环境执行结果的前提下,编译器和处理器可以进行重排序,这是合法的。换句话说,这一是规则无法保证编译重排和指令重排)。 2、监视器上的解锁操作 happen-before 其后续的加锁操作。(Synchronized 规则) 3、对volatile变量的写操作 happen-before 后续的读操作。(volatile 规则) 4、线程的start() 方法 happen-before 该线程所有的后续操作。(线程启动规则) 5、线程所有的操作 happen-before 其他线程在该线程上调用 join 返回成功后的操作。 6、如果 a happen-before b,b happen-before c,则a happen-before c(传递性)。 在JVM中,将Happens-Before的程序顺序规则与其他某个顺序规则(通常是监视器锁规则、volatile变量规则)结合起来,从而对某个未被锁保护的变量的访问操作进行排序。 我们着重看第三点volatile规则:对volatile变量的写操作 happen-before 后续的读操作。为了实现volatile内存语义,JMM会重排序,其规则如下: 是否重排序 第二个操作 第一个操作 普通读/写 volatile读 volatile写 普通读/写 volatile读 NO NO NO volatile写 NO NO 为了探究volatile底层的实现原理,进行了如下探究。 通过javap 命令,将字节码文件反编译。观察反编译的结果,对于volatile修饰的变量,发现反编译得到的代码并没有什么帮助,和不加volatile修饰的变量没有任何区别。也就是说,字节码层面volatile变量并没有什么不同。 下面通过查看Java的汇编指令,查看Java代码最真实的运行细节。 如何查看Java的汇编指令,可以阅读:https://www.jianshu.com/p/93821b08e774 通过使用-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly IDEA打印出了源代码的汇编指令。我们看到红色线框里面的那行指令:putstatic a ,将静态变量a入栈,注意观察add指令前面有一个lock前缀指令。 加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令。我们发现,volatile变量在字节码级别没有任何区别,在汇编级别使用了lock指令前缀。 lock是一个指令前缀,Intel的手册上对其的解释是: Causes the processor's LOCK# signal to be asserted during execution of the accompanying instruction (turns the instruction into an atomic instruction). In a multiprocessor environment, the LOCK# signal insures that the processor has exclusive use of any shared memory while the signal is asserted. 简单理解也就是说,lock后就是一个原子操作。原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。 当使用 LOCK 指令前缀时,它会使 CPU 宣告一个 LOCK# 信号,这样就能确保在多处理器系统或多线程竞争的环境下互斥地使用这个内存地址。当指令执行完毕,这个锁定动作也就会消失。 是不是感觉有点像Java的synchronized锁。但volatile底层使用多核处理器实现的lock指令,更底层,消耗代价更小。 因此有人将Java的synchronized看作重量级的锁,而volatile看作轻量级的锁 并不是全无道理。 lock前缀指令其实就相当于一个内存屏障。内存屏障是一组CPU处理指令,用来实现对内存操作的顺序限制。volatile的底层就是通过内存屏障来实现的。 编译器和执行器 可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。正如去西藏途中各个站点的先后顺序在你心中都一清二楚。 内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪个CPU执行的。这正是volatile实现内存可见性的基础。 内存屏障细说来有写屏障、读屏障、读写屏障,而且内存屏障的实现依赖于编译器和机器两部分。 编译器在编译过程中可能会对指令重排序,这样开发者通过显式地标注告知编译器,避免编译器最终生成的代码行为违背预期,对于 Java 而言,不光生成的 bytecode 需要保存 volatile 的语义,连运行时的 JIT 代码的行为也要遵守相应的约束;即插入内存屏障后,告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行,从而实现了禁止重排序。 关于内存屏障的一些具体细节,大佬Martin写了一篇文章《going into memory barriers》介绍,外网可以看看。 小结: 1、Java重排序的前提:在不影响 单线程运行结果的前提下进行重排序。也就是在单线程环境运行,重排序后的结果和重排序之前按代码顺序运行的结果相同。 2、指令重排序对单线程没有什么影响,它不会影响程序的运行结果,反而会优化执行性能,但会影响多线程的正确性。 3、Java因为指令重排序,优化我们的代码,让程序运行更快,也随之带来了多线程下,指令执行顺序的不可控。 4、volatile的底层是通过lock前缀指令、内存屏障来实现的。 存档文章 查看Java的汇编指令 终于有人把Java内存模型(JMM)说清楚了 JVM体系结构-----深入理解内存结构 从多核硬件架构,看Java内存模型

优秀的个人博客,低调大师

TypeScript VS JavaScript 深度对比

TypeScript 和 JavaScript 是目前项目开发中较为流行的两种脚本语言,我们已经熟知 TypeScript 是 JavaScript 的一个超集,但是 TypeScript 与 JavaScript 之间又有什么样的区别呢?在选择开发语言时,又该如何抉择呢? 本文将会深入对比这两种语言,讨论两种语言之间的关联和差异,并概述两种语言各自的优势。 JavaScript 和 TypeScript 的概要介绍 JavaScript JavaScript 是一种轻量级的解释性脚本语言,可嵌入到 HTML 页面中,在浏览器端执行,能够实现浏览器端丰富的交互功能,为用户带来流畅多样的用户体验。 JavaScript 是基于对象和事件驱动的,无需特定的语言环境,只需在支持的浏览器上就能运行。 JavaScript 语言具有以下特点: JavaScript 是一种脚本编写语言,无需编译,只要嵌入 HTML 代码中,就能由浏览器逐行加载解释执行。 JavaScript 是一种基于对象的语言,可以创建对象同时使用现有对象。但是 Javascript 并不支持其它面向对象语言所具有的继承和重载功能。 JavaScript 的语法简单,使用的变量为弱类型。 JavaScript 语言较为安全,仅在浏览器端执行,不会访问本地硬盘数据。 JavaScript 语言具有动态性。JavaScript 是事件驱动的,只根据用户的操作做出相应的反应处理。 JavaScript 只依赖于浏览器,与操作系统的因素无关。因此 JavaScript 是一种跨平台的语言。 JavaScript 兼容性较好,能够与其他技术(如 XML,REST API等)一起使用。 TypeScript TypeScript 是Microsoft 开发和维护的一种面向对象的编程语言。它是 JavaScript 的超集,包含了 JavaScript 的所有元素,可以载入 JavaScript 代码运行,并扩展了 JavaScript 的语法。 TypeScript 具有以下特点: TypeScript 是 Microsoft 推出的开源语言,使用 Apache 授权协议 TypeScript 增加了静态类型、类、模块、接口和类型注解 TypeScript 可用于开发大型的应用 TypeScript 易学易于理解 JavaScript 和 TypeScript 的主要差异 TypeScript 可以使用 JavaScript 中的所有代码和编码概念,TypeScript 是为了使 JavaScript 的开发变得更加容易而创建的。例如,TypeScript 使用类型和接口等概念来描述正在使用的数据,这使开发人员能够快速检测错误并调试应用程序 TypeScript 从核心语言方面和类概念的模塑方面对 JavaScript 对象模型进行扩展。 JavaScript 代码可以在无需任何修改的情况下与 TypeScript 一同工作,同时可以使用编译器将 TypeScript 代码转换为 JavaScript。 TypeScript 通过类型注解提供编译时的静态类型检查。 TypeScript 中的数据要求带有明确的类型,JavaScript不要求。 TypeScript 为函数提供了缺省参数值。 TypeScript 引入了 JavaScript 中没有的“类”概念。 TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中。 TypeScript 的优势 下面列举 TypeScript 相比于 JavaScript 的显著优势: 1. 静态输入 静态类型化是一种功能,可以在开发人员编写脚本时检测错误。查找并修复错误是当今开发团队的迫切需求。有了这项功能,就会允许开发人员编写更健壮的代码并对其进行维护,以便使得代码质量更好、更清晰。 2. 大型的开发项目 有时为了改进开发项目,需要对代码库进行小的增量更改。这些小小的变化可能会产生严重的、意想不到的后果,因此有必要撤销这些变化。使用TypeScript工具来进行重构更变的容易、快捷。 3. 更好的协作 当发开大型项目时,会有许多开发人员,此时乱码和错误的机也会增加。类型安全是一种在编码期间检测错误的功能,而不是在编译项目时检测错误。这为开发团队创建了一个更高效的编码和调试过程。 4. 更强的生产力 干净的 ECMAScript 6 代码,自动完成和动态输入等因素有助于提高开发人员的工作效率。这些功能也有助于编译器创建优化的代码。 JavaScript 的优势 相比于 TypeScript,JavaScript 也有一些明显优势。 1. 人气 JavaScript 的开发者社区仍然是巨大而活跃的,在社区中可以很方便地找到大量成熟的开发项目和可用资源。 2. 学习曲线 由于 JavaScript 语言发展的较早,也较为成熟,所以仍有一大批开发人员坚持使用他们熟悉的脚本语言 JavaScript,而不是学习 TypeScript。 3. 本地浏览器支持 TypeScript 代码需要被编译(输出 JavaScript 代码),这是 TypeScript 代码执行时的一个额外的步骤。 4. 不需要注释 为了充分利用 TypeScript 特性,开发人员需要不断注释他们的代码,这可能会使项目效率降低。 5. 灵活性 有些开发人员更喜欢 JavaScript 的灵活性。 如何抉择 TypeScript 正在成为开发大型编码项目的有力工具。因为其面向对象编程语言的结构保持了代码的清洁、一致和简单的调试。因此在应对大型开发项目时,使用 TypeScript 更加合适。如果有一个相对较小的编码项目,似乎没有必要使用 TypeScript,只需使用灵活的 JavaScript 即可。 JavaScript 开发工具推荐 SpreadJS纯前端表格控件是基于 HTML5 的 JavaScript 电子表格和网格功能控件,提供了完备的公式引擎、排序、过滤、输入控件、数据可视化、Excel 导入/导出等功能,适用于 .NET、Java 和移动端等各平台在线编辑类 Excel 功能的表格程序开发。 参考文章:https://dzone.com/articles/typescript-vs-javascript-should-you-migrate-your-j 转载请注明出自:葡萄城控件 原文发布时间为:2018年01月08日原文作者:葡萄城技术团队 本文来源:开源中国 如需转载请联系原作者

优秀的个人博客,低调大师

ELK日志管理深度实战

Logstash收集Rsyslog/syslog日志 syslog默认是通过514端口去发送日志,可以使用logstash安装对应的插件,监听514端口来收集日志。如果只是监听系统日志可以不用安装logstash的agent,只需要监听对应的514端口,收集系统数据即可。 logstash在INPUT插件中提供了syslog的模块,可以用于不安装本地日志收集agent的设备(如硬件防火墙等),当然支持此类协议的普通服务器也适用。 注意:此INPUT插件会同时监听TCP和UDP端口。 可以在安装logstash服务器node2(172.16.10.21)上编写logstash配置文件,指定端口(默认为514)同时指定写入的ES服务器: 1 2 3 4 5 6 7 8 9 10 11 12 13 [root@node2~] #cat/etc/logstash/conf.d/syslog.conf input{ syslog{ type => "system-syslog" port=>514 } } output{ elasticsearch{ hosts=>[ "172.16.10.21:9200" ] #指定写入的ES服务器 index=> "system-syslog-%{+YYYY.MM}" } } 在node1(172.16.10.20)使syslog自动发送日志信息到logstash,需要修改/etc/rsyslog.conf(CentOS7)的配置文件: 在最后添加一行,指定远程logstash服务器地址和端口: 1 *.*@@172.16.10.21:514 重启rsyslog服务: 1 systemctlrestartrsyslog 在logstash服务器上指定配置文件启动: 1 /opt/logstash/bin/logstash -f /etc/logstash/conf .d /syslog .conf 在node1使用logger 命令可以生成系统日志,使Elasticsearch上产生索引: 1 loggertrying 登录Elasticsearch上就可查询到syslog的日志了,可以在kibana上添加此项。 TCP、UDP日志收集 在有时要对一些日志进行补充或者发送一些文件内容到es上时,可以通过使用TCP模块的方式。 编辑tcp.conf的logstash文件: 1 2 3 4 5 6 7 8 9 10 11 12 13 [root@node2~] #cat/etc/logstash/conf.d/tcp.conf input{ tcp{ type => "tcp" port=> "6666" mode=> "server" } } output{ stdout{ codec=>rubydebug } } 启动服务,在其他任何主机上可以通过nc 命令或者伪设备输出的方式,将需要发送的内容发送给node2: 1 2 3 [root@node1~] #echo"trying"|nc172.16.10.216666 [root@node1~] #nc172.16.10.216666</etc/resolv.conf [root@node1~] #echo"stuff">/dev/tcp/172.16.10.21/6666 在node2上,显示接受的信息: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 [root@node2~] #/opt/logstash/bin/logstash-f/etc/logstash/conf.d/tcp.conf Settings:Defaultpipelineworkers:1 Pipelinemainstarted { "message" => "trying" , "@version" => "1" , "@timestamp" => "2017-01-03T08:12:10.630Z" , "host" => "172.16.10.20" , "port" =>57054, "type" => "tcp" } { "message" => "#GeneratedbyNetworkManager" , "@version" => "1" , "@timestamp" => "2017-01-03T08:13:11.081Z" , "host" => "172.16.10.20" , "port" =>57248, "type" => "tcp" } { "message" => "nameserver114.114.114.114" , "@version" => "1" , "@timestamp" => "2017-01-03T08:13:11.081Z" , "host" => "172.16.10.20" , "port" =>57248, "type" => "tcp" } { "message" => "stuff" , "@version" => "1" , "@timestamp" => "2017-01-03T08:15:14.224Z" , "host" => "172.16.10.20" , "port" =>57634, "type" => "tcp" } 使用Filter模块对Apache日志进行格式化 在logstash中可以对一些输入的格式进行格式化,如记录Apache日志时,由于自身不具有像nginx提供的JSON格式化的输出,所以在进行记录时,需要使用filter插件的grok来进行正则匹配。 在logstash中,已经默认提供了方便使用的正则表示方式,在/opt/logstash/vendor/bundle/jruby/1.9/gems/logstash-patterns-core-2.0.5/patterns保存了相关的配置。 编写logstash的配置文件: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@node2patterns] #cat/etc/logstash/conf.d/grok.conf input{ stdin{} } filter{ grok{ match=>{ "message" => "%{IP:client}%{WORD:method}%{URIPATHPARAM:request}%{NUMBER:bytes}%{NUMBER:duration}" } } } output{ stdout{ codec=>rubydebug } } 官方文档提供了示例,可以直接使用https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html 启动服务,输入测试示例,返回格式化的输出: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [root@node2~] #/opt/logstash/bin/logstash-f/etc/logstash/conf.d/grok.conf Settings:Defaultpipelineworkers:1 Pipelinemainstarted 55.3.244.1GET /index .html158240.043 #输入的示例 { "message" => "55.3.244.1GET/index.html158240.043" , "@version" => "1" , "@timestamp" => "2017-01-03T08:59:44.264Z" , "host" => "node2" , "client" => "55.3.244.1" , "method" => "GET" , "request" => "/index.html" , "bytes" => "15824" , "duration" => "0.043" } 使用grok收集Apache日志到Elasticsearch. 在默认的Apache日志格式下,logstash提供了一个已经编写好的日志格式正则filter: 1 2 3 4 [root@node2~] #cat/opt/logstash/vendor/bundle/jruby/1.9/gems/logstash-patterns-core-2.0.5/patterns/grok-patterns\ | grep "COMBINEDAPACHELOG" COMBINEDAPACHELOG%{COMMONAPACHELOG}%{QS:referrer}%{QS:agent} 编写logstash配置文件,引用此参数COMBINEDAPACHELOG: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [root@node2~] #cat/etc/logstash/conf.d/grok.conf input{ file { path=> "/var/log/httpd/access_log" start_position=> "beginning" } } filter{ grok{ match=>{ "message" => "%{COMBINEDAPACHELOG}" } #调用此参数 } } output{ elasticsearch{ hosts=>[ "172.16.10.20:9200" ] index=> "apache-access-log-%{+YYYY.MM.dd}" } #stdout{codec=>rubydebug} } 这样,默认的日志文件就可以被格式化输出到Elasticsearch上了。 使用grok会有一些问题,比如会非常影响性能,而且很不灵活,除非对ruby掌控很好。 在实际的生产环境中,为了实现松耦合和性能问题,一般将日志写入redis,再使用python对其进行格式化处理。 使用消息队列扩展ELK 数据 >> lostash >> MQ/Redis >> logstash >> ES 在实现低耦合和高可靠性的ELK部署架构中,使用消息队列的方式来作为一个中间转存的数据交换中心,将数据文件需要写入到消息队列,而对消息队列中数据的处理可有其它任意的应用去实现,整个过程写入和输出系统没有任何依赖关系。 这里使用最简单的redis来实现这一功能。 在node2上安装redis,将收集的数据发送到redis中,不做任何处理,这里需要先修改redis的配置文件: 1 2 3 #vim/etc/redis.conf daemonize yes #使用台运行模式 bind172.16.10.21 #绑定监听的IP 通过使用logstash将数据写入redis,创建logstash的配置文件,用于将数据写入redis: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@node2~] #cat/etc/logstash/conf.d/redis.conf input{ stdin{ } } output{ redis{ host=> "172.16.10.21" port=> "6379" db=> "6" data_type=> "list" key=> "demo" } } 启动redis,指定配置文件启动logstash: 1 systemctlstartredis 1 2 3 4 [root@node2~] #/opt/logstash/bin/logstash-f/etc/logstash/conf.d/redis.conf Settings:Defaultpipelineworkers:1 Pipelinemainstarted try 在redis上可以查看到输入的数据: 1 2 3 4 5 6 7 8 9 10 11 [root@node2~] #redis-cli-h172.16.10.21-p6379 172.16.10.21:6379>info #Keyspace db6:keys=2,expires=0,avg_ttl=0 172.16.10.21:6379> select 6 OK 172.16.10.21:6379[6]>keys* 1) "apache-accesslog" 2) "demo" 172.16.10.21:6379[6]>lindexdemo-1 "{\"message\":\"try\",\"@version\":\"1\",\"@timestamp\":\"2017-01-04T03:29:28.337Z\",\"host\":\"node2\"}" 使用另一个logstash将数据从redis中取出: 在node1上配置logstash的配置文件,指定文件启动,此时会自动将redis上的数据读出: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@node1~] #cat/etc/logstash/conf.d/redis-get.conf input{ redis{ host=> "172.16.10.21" port=> "6379" db=> "6" data_type=> "list" key=> "demo" } } filter{} output{ stdout{ codec=>rubydebug } } 1 2 3 4 5 6 7 8 9 [root@node1conf.d] #/opt/logstash/bin/logstash-f/etc/logstash/conf.d/redis-get.conf Settings:Defaultpipelineworkers:1 Pipelinemainstarted { "message" => "try" , "@version" => "1" , "@timestamp" => "2017-01-03T12:10:20.635Z" , "host" => "node2" } 上面使用的是手动输入的方式,在实际收集日志时,可以在输入时指定文件,输出时指定到Elasticsearch。 使用Redis收集Apache访问日志 在上面的示例中,我们使用了直接通过grok来收集Apache的日志,但是在实际的生产环境,由于这种方案会使性能下降,而且在整个架构上过度依赖于logstash,无法做到解耦,并不是一个好的解决方案。这里引入消息队列,作为一个解耦,上日志的存取分开,方便后期扩展,提高了稳定性。 在node2上配置logstash 配置文件,指定Apache的access文件,并启动: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@node2~] #cat/etc/logstash/conf.d/apache-redis.conf input{ file { path=> "/var/log/httpd/access_log" start_position=> "beginning" } } output{ redis{ host=> "172.16.10.21" port=> "6379" db=> "6" data_type=> "list" key=> "apache-log" } } 此处直接完整输入内容到redis,不做任何处理。 在node1上从redis中取出日志,并通过grok进行日志格式化处理,之后输出到Elasticsearch中。 node1上logstash的配置: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@node1~] #cat/etc/logstash/conf.d/redis-get.conf input{ redis{ host=> "172.16.10.21" port=> "6379" db=> "6" data_type=> "list" key=> "apache-log" } } filter{ grok{ match=>{ "message" => "%{COMBINEDAPACHELOG}" } } } output{ elasticsearch{ hosts=>[ "172.16.10.20:9200" ] index=> "apache-log-%{+YYYY.MM.dd}" } } 这样,启动node1和node2上的logstash,在指定的Elasticsearch上就可以看到被格式化后的日志输出了。 和redis类似,可以使用Kafka作为存储数据的中间节点:https://www.unixhot.com/article/61 ELK项目生产规划 在实际的生产环境中要对日志进行收集,首先需要对日志分类: 访问日志: apache,nginx, tomcat 等web访问日志 通过file插件收集,apache通过filter插件处理格式 错误日志:error log,java 日志 等可以直接收取,对于一条多行的日志(java异常)使用多行处理插件 系统日志: /var/log syslog 等 直接使用 syslog插件收取 网络日志: 防火墙,交换机,路由器等 使用 syslog日志 运行日志:应用程序定义 使用file 插件, 最好为json格式 标准化: 统一存放路径,命名规则,格式(json),日志切割(crontab). 需要保存的日志先在本地读写,提高性能,通过rsync推送到指定存储,防止挂载故障而使服务终止。删除本地日志(确认保留时间) 工具化:可以使用logstash进行日志收集方案。 如果使用redis作为消息队列,那么要对所有的list key的长度进行监控,防止溢出和数据丢失 llen key_name 根据实际情况,例如超过 10万 就报警。 如将之前的所有配置合并到一处,在使用redis作为存储时,可以这样编辑logstash配置文件: 从redis服务器接受日志,处理后存入Elasticsearch: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 input{ syslog{ type => "system-syslog" port=>514 } redis{ type => "apache-accesslog" host=> "192.168.56.12" port=> "6379" db=> "6" data_type=> "list" key=> "apache-accesslog" } redis{ type => "es-log" host=> "192.168.56.12" port=> "6379" db=> "6" data_type=> "list" key=> "es-log" } } filter{ if [ type ]== "apache-accesslog" { grok{ match=>{ "message" => "%{COMBINEDAPACHELOG}" } } } } output{ if [ type ]== "apache-accesslog" { elasticsearch{ hosts=>[ "192.168.56.11:9200" ] index=> "apache-accesslog-%{+YYYY.MM.dd}" } } if [ type ]== "es-log" { elasticsearch{ hosts=>[ "192.168.56.11:9200" ] index=> "es-log-%{+YYYY.MM}" } } if [ type ]== "system-syslog" { elasticsearch{ hosts=>[ "192.168.56.11:9200" ] index=> "system-syslog-%{+YYYY.MM}" } } } 从服务器接收日志,将日志存入redis/MQ: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 input{ file { path=> "/var/log/httpd/access_log" start_position=> "beginning" type => "apache-accesslog" } file { path=> "/var/log/elasticsearch/elasticsearch.log" type => "es-log" start_position=> "beginning" codec=>multiline{ pattern=> "^\[" negate=> true what=> "previous" } } } output{ if [ type ]== "apache-accesslog" { redis{ host=> "192.168.56.12" port=> "6379" db=> "6" data_type=> "list" key=> "apache-accesslog" } } if [ type ]== "es-log" { redis{ host=> "192.168.56.12" port=> "6379" db=> "6" data_type=> "list" key=> "es-log" } } } 这里使用了if做为了条件的判断,来对不同的日志进行选择处理,在一些系统日志如syslog等,可以单独进行处理。 提示: 在使用系统自带的脚本进行启动时,会出现无法收集日志的情况,脚本中默认的是使用logstash用户,在收集一些日志时可能存在没有权限的情况。可以修改脚本中的默认用户,或者给logstash添加其他用户组的权限。 本文转自 酥心糖 51CTO博客,原文链接:http://blog.51cto.com/tryingstuff/1888978

优秀的个人博客,低调大师

深度学习的IR“之争”

熟悉编译器的同学应该对上图并不陌生。它就是大名鼎鼎的LLVM的logo。Google Tensorflow XLA (Accelerated Linear Algebra)就使用了LLVM IR(Intermediate Representation)。而它的“竞争对手”,刚刚发布的TVM/NNVM,则是“Tensor IR Stack for Deep Learning Systems”。IR是什么?为什么重要?我们一起来看看。 上周,我们看到这样的新闻“Facebook and Microsoft introduce new open ecosystem for interchangeable AI frameworks”。这也让Framework之争更加热闹。简单来说,ONNX也是为了解决目前多个Framework互操作的问题

优秀的个人博客,低调大师

深度解析阿里云存储

国际知名调研机构Gartner近日公布了2017年全球云计算云存储魔力象限,阿里云的云存储强势崛起成为这一核心领域的前四名。 图1 2017年Gartner全球云存储魔力象限图 在去年首次进入Gartner魔力象限即取得了不错的位置之后,今年阿里云存储再次强势进入公共云存储魔力象限,紧跟Google成为公共云存储厂商中在利基象限中最接近领导者象限的公共云存储厂商,而领导者象限中目前只有AWS和Azure。 图2 2016年Gartner全球云存储魔力象限图 作为国内市场排名第一的云厂商的云存储产品系列,取得这样的成绩也并不是水涨船高的结果。首先从整体评估上来说,Gartner的评估方法并不依赖母体厂商的品牌和占有率,而是客观的从云存储类型(对象,块,文件)和冷热存储,全球数据中心的位置、技术成熟度、市场策略、市场份额、服务承诺和SLA

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册