Java 查漏补缺之泛型

简而言之,泛型使类型(类和接口)在定义类,接口和方法时成为参数。类型参数提供了一种简便的方法,使得不同的输入类型可以使用相同的代码。

使用泛型的代码比非泛型代码有如下好处:

  • 在编译时进行更强大的类型检查
  • 消除了强制类型装换
  • 可以让编程人员更加容易实现通用算法

定义

通常情况下,泛型类 (generic class) 可以如下定义

class name<T1, T2, ..., Tn> { /* ... */ }

尖括号中的类型通常称为类型参数( type parameters 或者称为类型变量 type variables),一旦类定义了类型参数T ,该变量就能在类中使用。

Type Parameter 命名约定

按照惯例, type parameters 的名字都是单一的大写字母,通常和普通的命令规范 区别开来。

经常被使用的泛型变量有:

  • E for Element 被 Java Collections 框架广泛使用
  • K for Key
  • N for Number
  • T for Type
  • V for Value
  • S,U,V etc - 2nd, 3rd, 4th types

调用和实例化泛型类型

泛型的调用其实非常简单,只需要将类型参数替换为具体的类型即可,比如

List<String> list;

Type Parameter 和 Type Argument 术语:大部分情况下这两个术语是可以互换的,但他们的使用场景是不一样的。因此 Foo<T> 中的 T 是 type Parameter,而 Foo 中的 String 是 type argument.

泛型方法

对于静态泛型方法(static generic method), type parameter 定于的区域需要出现在返回值的前面

public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {...}

调用该方法的完整的方式

boolean same = Util.<Integer, String>compare(p1, p2);
// or
boolean same = Util.compare(p1, p2);

有界类型参数

总有一种情况,编程人员想要限制泛型的类型,比如一个操作数字的类或者方法,可能希望泛型只接受 Number 或者其子类的实例。

定义上界,比如<E extends Comparable> 在这个例子中,表示定义的泛型需要实现 Comparable 接口,这里的 extends 只是通用表示 extends (Class) 或者 implements (interfaces)

如果要定义多个界

<T extends B1 & B2 & B3>

在定义多个届时,需要将 Class 类型放到 interface 之前,比如说上面的例子中假如有 Class B2, interface B1 & B3 ,那么 B2 必须是第一个。

通配符 #{wildcards}

在代码中也经常能看到 ? 问号,通常叫做通配符(wildcard),表示是类型未知。通配符可以用在非常多的场景,作为参数,field,或者本地变量,有时候也作为返回值(当然不推荐这么做)。

Upper Bounded Wildcards

public static void process(List<? extends Foo> list) { /* ... */ }

upper bounded wildcard,List<? extends Foo> 其中 Foo 是类型,表示 Foo 和任何子类。

Unbounded Wildcards

无界通配符类型(upper bounded wildcard),通常表示的单纯的使用 ,比如 List<?>,表示的是一个不知道类型的 list。有两种常用的使用场景

  • 当写方法只需要 Object 类中的方法时
  • 在泛型类中定义的方法不依赖于 type parameter 时,比如 List.size() 或者 List.clear ,并不依赖于定义的泛型类型

举一个官方文档中的例子,假设有如下的方法

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

本意上是想要实现打印一个任何类型的列表,但是并不能达到目的,他并不能打印 List<Integer>, List<String> 等,因为他们并不是 List<Object> 的子类型,如果要实现通用的 printList 方法需要使用 List<?>

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

这个时候 List<Integer> 才是 List<?> 的子类型。

Lower Bounded Wildcards

类型的上界使用 extends,相同的,如果要表示类型的下界,则使用 super,比如 <? super A> 。需要注意的是,对于通配符 ? 可以单独指定上界,也可以指定下界,但是两者不能同时指定。

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

代码可以在 List<Integer>, List<Number>, and List<Object> — anything that can hold Integer values. 上运行。

假设

class A { /* ... */ }
class B extends A { /* ... */ }

都知道 B 是 A 的子类型,所以可以

B b = new B();
A a = b;

所以对于泛型类型呢?

List<B> listB = new ArrayList<>();
List<A> listA = listB;        // 编译错误

List<Number>List<Integer> 都是 List<?> 的子类型,而 List<Number>List<Integer> 这两者并没有任何关系。

为了让两者有关系,就需要用多泛型的上界,这样

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

下面有一张图来表示 List 类型的父子类关系

a hierarchy of several generic list class declarations

类型擦除

泛型被引入到 Java 语言中,以便在编译时提供更严格的类型检查并支持泛型编程。为了实现泛型,Java 编译器使用类型擦除:

  • 如果类型参数是无界的,则将泛型类型中的所有类型参数替换为其边界或对象。因此,生成的字节码仅包含普通的类,接口和方法。
  • 插入类型转换,以保证类型安全
  • 生成桥接方法以保留扩展泛型类型中的多态性

类型擦除确保不为参数化类型创建新类;因此,泛型不会产生运行时开销。

在类型擦除过程中,Java 编译器会擦除所有的 type parameters,如果是有界的类型参数则替换成第一个类型,如果是无界的类型参数则替换为 Object。

Java 编译器还会擦除泛型方法参数中的类型参数。比如静态方法:

// Counts the number of occurrences of elem in anArray.
//
public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

因为 T 是无界的,则会把 T 出现的地方全部替换为 Object。和泛型类相同,有界的方法中的类型参数也会替换为第一个类型参数。

泛型的限制

不能使用原始类型来实例化泛型

比如

Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error

会有编译错误。Java 会自动装箱。

无法创建类型参数的实例

比如

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

作为解决方案,可以使用反射来创建对象实例

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

如下调用

List<String> ls = new ArrayList<>();
append(ls, String.class);

无法申明类型参数为静态

如下是不对的

public class MobileDevice<T> {
    private static T os;

    // ...
}

不能对 parameterized types 进行类型装换和使用 instanceof

因为 Java 编译器会在编译时擦除类型,所以无法验证参数类型在运行时的类型。下面是错误的例子:

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}

而对于有界的参数化类型,可以使用

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        // ...
    }
}

无法创建参数化类型的数组

不能

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

无法创建,捕获或抛出参数化类型的对象

泛型类无法直接或者间接继承 Throwable 。

// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ }    // compile-time error

// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error

但是可以在 throw 语句后使用

class Parser<T extends Exception> {
    public void parse(File file) throws T {     // OK
        // ...
    }
}

不能有重载的方法使用相同的泛型

比如

public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

reference


2018-07-18 java , generics , programming , linux

Spring 相关资源

收集了一些官方的非官方的学习资源。

文档

代码

Spring 深度探险

第一篇 Spring MVC 前传

Spring MVC 前传

主要讲了 Spring 发展的历史,和 struct 等的对比

第二篇 Spring MVC 概览

Spring MVC 概览

以 Servlet 作为入口程序是绝大多数 MVC 框架都遵循的基本设计方案,这里的 DispatcherServlet 被称之为核心分发器。Spring MVC 的核心配置文件定义在 [servlet-name]-servlet.xml 文件中。控制层 Controller 是一个单独的 Java 文件,只是使用注解将其与 HTTP 请求关联。

- 入口程序 DispatcherServlet
- 核心配置 servlet.xml
- 控制逻辑 Controller

这三者构成 Spring MVC 的基础要素。

SpringMVC 在整个 Controller 改造中所涉及到的一些要点:

  1. 使用参数 - 返回值(Param-Return)实现模式来打造 Controller
  2. 引入 Annotation 来完成请求 - 响应的映射关系

    引入 Annotation 来完成请求 - 响应的映射关系,是 SpringMVC 的一个重大改造。在早期的 SpringMVC 以及其他的 MVC 框架中,通常都是使用 XML 作为基础配置的。而 Annotation 的引入将原本分散的关注点合并到了一起,为实现配置简化打下了坚实的基础。

  3. 泛化参数和返回值的含义

    核心 Servlet 应该能够根据一定的规则对不同的 Http 请求分发到不同的 Servlet 对象上去进行处理。核心 Servlet 应该能够建立起一整套完整的对所有 Http 请求进行规范化处理的流程。

Servlet 模型中,请求 和 响应实现依赖,web.xml 中配置 <servlet><servlet-mapping> 的对应,将响应的 URL pattern 和其对应的实现类进行关联。针对这个问题,Spring MVC 提出的方案就是提炼一个核心的 Servlet 覆盖对所有 HTTP 请求的处理,这就是 org.springframework.web.servlet.DispatcherServlet 核心分发器。

因此 web.xml 中的配置就被固定

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/**</url-pattern>
</servlet-mapping>

Spring MVC 接下来要处理的内容就是根据一定的规则处理不同的 HTTP 请求,分发到响应的 Servlet 处理。

在 SpringMVC 的设计中,这两个方面的内容总是在一个不断交叉、互为补充的过程中逐步完善的。

处理流程规范化是目的,对于处理过程的步骤划分和流程定义则是手段。因而处理流程规范化的首要内容就是考虑一个通用的 Servlet 响应程序大致应该包含的逻辑步骤:

  • 步骤 1 —— 对 Http 请求进行初步处理,查找与之对应的 Controller 处理类(方法)
  • 步骤 2 —— 调用相应的 Controller 处理类(方法)完成业务逻辑
  • 步骤 3 —— 对 Controller 处理类(方法)调用时可能发生的异常进行处理
  • 步骤 4 —— 根据 Controller 处理类(方法)的调用结果,进行 Http 响应处理

在 Java 语言中,最适合表达逻辑处理语义的语法结构是接口,因此上述的四个流程也就被定义为了四个不同接口,它们分别是:

  • 步骤 1 —— HandlerMapping
  • 步骤 2 —— HandlerAdapter
  • 步骤 3 —— HandlerExceptionResolver
  • 步骤 4 —— ViewResolver

核心配置文件在整个 SpringMVC 的构成要素中占有一席之地的重要原因就是在于:必须借助一个有效的手段对整个 SpringMVC 的组件进行定义,而这一点正是通过核心配置文件来完成的。

第三篇 DispatcherServlet 与初始化主线

DispatcherServlet 与初始化主线,SpringMVC 的设计原则 Open for extension / closed for modification

Spring MVC 设计中的要点

  • SpringMVC 将 Http 处理流程抽象为一个又一个处理单元
  • SpringMVC 定义了一系列组件(接口)与所有的处理单元对应起来
  • SpringMVC 由 DispatcherServlet 贯穿始终,并将所有的组件串联起来

根据 Servlet 规范的定义,Servlet 中的两大核心方法 init 方法和 service 方法,它们的运行时间和触发条件都截然不同:

init 方法

在整个系统启动时运行,且只运行一次。因此,在 init 方法中我们往往会对整个应用程序进行初始化操作。这些初始化操作可能包括对容器(WebApplicationContext)的初始化、组件和外部资源的初始化等等。

service 方法

在整个系统运行的过程中处于侦听模式,侦听并处理所有的 Web 请求。因此,在 service 及其相关方法中,我们看到的则是对 Http 请求的处理流程。

DispatcherServlet 的初始化主线

初始化主线的驱动要素,servlet 中的 init 方法;初始化主线的执行次序,HttpServletBean->FrameworkServlet->DispatcherServlet;初始化主线的操作对象,Spring 容器(WebApplicationContext)和组件。 一个组件的多种行为模式可以在容器中共存,容器将负责对这些实现类进行管理。DispatcherServlet 中对于组件的初始化过程实际上是应用程序在 WebApplicationContext 中选择和查找组件实现类的过程,也是指定组件在 SpringMVC 中的默认行为方式的过程。

第四篇 SpringMVC 核心配置文件详解

SpringMVC 核心配置文件详解

SpringMVC 的核心配置文件在整个应用程序中所起到的作用也是举足轻重的。在 web.xml 中指定 SpringMVC 的入口程序 DispatcherServlet 时,实际上蕴含了一个对核心配置文件的指定过程([servlet-name]-servlet.xml)

namespace     element             attributes
 |             |                         |
<mvc:annotation-driven ignore-default-model-on-redirect="true"/>
 |             |                         |
逻辑分类     过程语义             行为配置选项

Schema-based XML 中的配置节点拥有比较鲜明的功能特性,通过 namespace、element 和 attributes 这三大元素之间的配合,共同完成对一个动态过程的描述。这段配置想要表达的意思,就是在 mvc 的空间内实现 Annotation 驱动的配置方式。其中,mvc 表示配置的有效范围,annotation-driven 则表达了一个动态的过程,实际的逻辑含义是:整个 SpringMVC 的实现是基于 Annotation 模式,请为我注册相关的行为模式。


2018-07-12 spring , spring-mvc , docs , demo , tutorial

Java 查漏补缺之 Class.isAssignableFrom() 和 instanceof 区别

Class.isAssignableFrom() 是用来判断一个类 Class1 和另一个类 Class2 是否相同或是另一个类的超类 superclass 或接口 superinterface。

调用方式: X.class.isAssignableFrom(Y.class)

调用者和参数都是 java.lang.Class 类型。上面例子,如果返回为 true,则表示 X 是 Y 的超类或者接口,Y 可以是一个类也可以是一个接口。

instanceof 是用来判断一个对象实例是否是一个类或接口的或其子类子接口的实例。

调用方式:o instanceof TypeName

第一个参数是对象实例名,第二个参数是具体的类名或接口名

延伸

Class 中还有一个相关的方法 isInstance() ,这个方法用来检查实例

MyClass.class.isInstance(obj)

如果返回 true,则表示 obj 是类 MyClass 的实例或者子类实例。

public class NewMain
{
    public static void main(String[] args)
    {
        NewMain nm = new NewMain();
        nm.doit();
    }

    public void doit()
    {
        A myA = new A();
        B myB = new B();
        A[] aArr = new A[0];
        B[] bArr = new B[0];

        System.out.println("b instanceof a: " + (myB instanceof A));
        System.out.println("b isInstance a: " + A.class.isInstance(myB));
        System.out.println("a isInstance b: " + B.class.isInstance(myA));
        System.out.println("b isAssignableFrom a: " + A.class.isAssignableFrom(B.class));
        System.out.println("a isAssignableFrom b: " + B.class.isAssignableFrom(A.class));
        System.out.println("bArr isInstance A: " + A.class.isInstance(bArr));
        System.out.println("bArr isInstance aArr: " + aArr.getClass().isInstance(bArr));
        System.out.println("bArr isAssignableFrom aArr: " + aArr.getClass().isAssignableFrom(bArr.getClass()));
    }

    class A
    {
    }

    class B extends A
    {
    }
}

结果是:

b instanceof a: true
b isInstance a: true
a isInstance b: false
b isAssignableFrom a: true
a isAssignableFrom b: false
bArr isInstance A: false
bArr isInstance aArr: true
bArr isAssignableFrom aArr: true

reference


2018-07-10 java , linux , object , class

maven plugins

maven-compiler-plugin

pom 文件定义,默认的 source 和 target 都是 1.5。

<project>
  [...]
  <build>
    [...]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <fork>true</fork>
          <verbose>true</verbose>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
    [...]
  </build>
  [...]
</project>

maven-surefire-plugin

pom 文件中定义,需要 Maven 2.2.1 or 3.x, and JDK 1.6 or higher

<project>
  [...]
  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
  [...]
</project>

在 mvn 的生命周期中 ,mvn test 时会调用该插件。如果要在 build 时跳过 test 可以定义

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.0</version>
    <configuration>
        <skip>true</skip>
    </configuration>
</plugin>

另外如果要添加或者排除某些目录可以参考官网 的例子。

maven-release-plugin

可用于构建 release 版本项目,实现自动打 tag、递增版本号、分发 release 版本 jar 包至仓库。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <version>2.5</version>
</plugin>

一般来说引入 maven release plugin 需要如下几步:

  • 在项目的 pom 文件中增加,无需添加 mvn-release-plugin 的依赖,因为它默认被包含于 maven 的 effective pom 中;
  • 检查自己的 maven 的 settings.xml 是否包含了私服的用户名密码;
  • 确保自己本地代码是在主分支,并且是最新的副本;
  • 执行 mvn release:prepare, 这时插件会扫描项目依赖查看是否有 SNAPSHOT, 是否存在未提交的文件,确定当前 release 的版本号和下一个迭代的版本号,插件会运行单元测试,并向 git 中提交两次 commit, 一次是 release 版本,一次是下一个迭代的版本。并将当前 release 版本打一个 tag 并提交到 git 上面去;
  • 执行 mvn release:perform, 插件会执行 mvn deploy 操作,并 clean 掉生成的缓存文件。

pom 设置

<distributionManagement>
    <repository>
        <id>releases</id>
        <name>xxxx internal releases repository</name>
        <url>http://xxx.com/nexus/content/repositories/releases</url>
    </repository>
    <snapshotRepository>
        <id>snapshots</id>
        <name>xxxx internal snapshots repository</name>
        <url>http://xxx.com/nexus/content/repositories/snapshots</url>
    </snapshotRepository>
</distributionManagement>

<scm>
    <url>http://xxx.com/project</url>
    <connection>scm:git:git@xxx.com/project.git</connection>
    <developerConnection>scm:git:git@xxx.com/project.git</developerConnection>
    <tag>HEAD</tag>
</scm>

reference


2018-07-09

phpMyAdmin 登录无反应问题记录

今天遇到一个神奇的错误,登录 phpMyAdmin 无论如何都登录不进去,开始还以为密码记错了,但是再输入错误密码时,会显示错误的提示

#1045 - Access denied for user 'root'@'localhost' (using password: YES)

而在在我确认密码没有问题时,phpMyAdmin 的表现就像是登录成功过了,但是却没有跳转。

然后为了解决这个问题,修改过 php.ini 中的 session.save_path ,修改了 session 文件的 权限 chmod 777 /path/to/session ,重启了 apache,尝试了所有的可能,直到看到一个解决方法是,因为浏览器的问题,换别的浏览器就能登录,我果断用 Vivaldi 试了一下,果然可以。

于是这个奇怪的问题就解决了,然后我清理了一下 Chrome 的 Cookie ,Session 然后就好了。至于是 phpMyAdmin 的问题还是 Chrome 的问题我也就无从得知了。奇怪了。


2018-07-03 php , phpmyadmin , mysql , root , password , browser

Java 查漏补缺:线程间通信

wait notify

方法 wait() 是让当前执行代码的线程等待,wait() 是 Object 类的方法。在调用 wait() 方法前,线程需要获得对象级别锁,只能在同步方法或者同步块中调用,执行 wait() 方法后,当前线程释放锁。如果调用时没有持有适当锁,会抛出 IllegalMonitorStateException 异常。

方法 notify() 也需要在同步方法或者同步块中调用,同样调用前也需要获取对象锁。如果调用没有持有适当的锁,也会抛出 IllegalMonitorStateException 异常。notify() 方法永安里通知可能处于等待该对象锁的其他线程,如果有多个线程等待,由线程规划器随机挑选一个呈现 wait 状态的线程,发出 notify 通知,并使它获得该对象的对象锁。notify() 方法执行后,当前线程不会马上释放该对象锁,wait 状态的线程也不能马上获取该对象锁,需要等执行 notify() 方法的线程将程序执行完,也就是退出 synchronized 代码块之后,当前线程才会被释放锁。

如果发出 notify() 操作时没有处于阻塞状态的线程,命令会被忽视。

wait() 方法使线程停止运行,而 notify() 使停止的线程继续运行。

join

如果主线程要等待子线程执行完成之后结束,可以使用 join() 方法。

join(long) 在内部使用 wait(long) 方法实现,所以会释放锁。而 Thread.sleep(long) 方法不会释放锁。

ThreadLocal

ThreadLocal 类主要解决的就是每个线程绑定自己的值,每个线程都有自己的私有数据。

公平锁 vs 非公平锁

公平锁线程在获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得 FIFO ,而非公平锁就是获取锁的抢占机制,随机获的锁。

ReentrantReadWriteLock

类 ReentrantLock 是完全互斥排他,同一时间只有一个线程能够执行 ReentrantLock 后任务,这样的方式保证了实例变量的线程安全,但是效率低下。所以 JDK 中提供了另外一个读写锁 ReentrantReadWriteLock ,在某些不需要操作实例变量的方法中,可以使用读写锁 ReentrantReadWriteLock 来提升方法的效率。

读写锁表示有两个锁,一个读相关锁,也成为共享锁,另一个是写相关的锁,也叫排它锁。读锁之间不排斥,写锁与其他锁互相排斥。多个线程可以同时读操作,但是同一时刻只允许有一个线程写操作。


2018-07-02 java , linux , thread , synchronized

通过 WebDAV 连接 NextCloud

NextCloud 支持 WebDAV 协议,用户可以完全通过 WebDAV 来连接并同步文件。虽然官方还是推荐 使用客户端来同步文件,不过如果要临时访问 NextCloud 上的文件,使用 WebDAV 方式还是很便捷的。

假设 NextCloud 的地址是 example.com 这个,那么在 Linux 下,一般文件管理器中有连接到服务器的选项在其中,填入服务器 IP 地址,访问端口,然后访问地址填写 /remote.php/dav/files/[USERNAME] 输入该用户的用户名和密码即可访问该用户的所有文件列表。

如果要直接在文件管理器中访问,可以尝试使用如下的协议地址:

dav://[USERNAME]@[SERVER_IP]:[PORT]/remote.php/dav/files/[USERNAME]

在 Linux 下还能通过命令行访问,具体可参考下面的链接。

reference


2018-07-01 nextcloud , webdav , dav , sync , files , linux

每天学习一个命令:使用 jpegoptim 和 optipng 优化压缩图片

今天突然遇到一个问题,使用手机拍摄的照片 5+M 体积巨大,但是网易要求的图片大小需要小于 1M,所以就有了压缩图片的需求。记得很久以前使用过 TinyPNG 这个服务,但是唯一一点不好的就是无法脱离他在线的服务,需要把照片上传到他的网站上才能进行压缩。虽然有 tinypng-cli 这个命令行的工具,但其实也是依赖在线服务的。所以就找了一下,然后发现了 jpegoptim 和 optipng 这两个工具。

jpegoptim

安装

apt-get install jpegoptim

使用

jpegoptim file.jpg

然后使用 du -h file.jpg 查看文件大小。使用这种方式 jpegoptim 会尽量使用无损失的压缩方式,所以几乎看不出区别。而如果想要指定大小,比如想要压缩到 500k 大小,可以

jpegoptim --size=500k file.jpg

而如果要批量压缩,在目录中 jpegoptim *.jpg 即可。

如果要将 EXIF 信息移走可以添加参数

jpegoptim --strip-exif file.jpg

结合 find

find images_folder/ *.{jpeg,jpg} -exec jpegoptim {} \;

optipng

安装

apt-get install optipng

使用

optipng file.png

结合 find

find images_folder/ *.png -exec optipng {} \;

reference


2018-06-30 compress , optimize , linux , command , jpeg , png

Java 查漏补缺之位运算符

今天在看 Kafka 源码 LongSerializer 时看到将 Long 型转 byte 数组的实现,感觉需要补习一下位运算符,所有有了这篇。

Java 提供的位运算符有:左移 ( << )、右移 ( >> ) 、无符号右移 ( >>> ) 、位与 ( & ) 、位或 ( | )、位非 ( ~ )、位异或 ( ^ ),除了位非 ( ~ ) 是一元操作符外,其它的都是二元操作符。

与或非,亦或 就不说了。

左移

左移 1 位 (<<1) ,值乘 2,以后依次乘 2.

右移

右移 1 位 (>>1),值除以 2。

无符号右移

对于负数,二进制高位为 1,所以一个 int 类型的 -5 二进制表示是

1111 1111 1111 1111 1111 1111 1111 1011

对于负数,使用右移 >> 时,则高位补 1,而对于无符号右移,则高位补 0 。

延伸

由位运算操作符衍生而来的有:

  • &= 按位与赋值
  • |= 按位或赋值
  • ^= 按位非赋值
  • >>= 右移赋值 a »= 1 就是 a = a » 1
  • <<= 赋值左移
  • >>>= 无符号右移赋值

+= 一样,理解为 a += 1 也就是 a = a + 1。


2018-06-26 java , bit , operator , notes

Python Linux 系统管理与自动化运维读书笔记

小工具

HTTP Server

python -m SimpleHTTPServer

python -m http.server

JSON 格式化

echo '<json string>' | python -m json.tool

验证第三方库安装

python -c "import paramiko"

pip 用法

源码安装

python setup.py install

pip 子命令

子命令 说明
search 搜索 pip search flask
install 安装 pip install flask==0.8 , pip install -r requirements.txt
uninstall 卸载
show 查看包详情
check pip 9.0.1 之后提供,检查包是否完整
list 列出已安装
freeze 导出已安装包列表 pip freeze > requirements.txt
completion 生成命令补全配置 pip completion -z >> ~/.zshrc && source ~/.zshrc

加速 pip 安装

下载时指定

pip install -i https://pypi.douban.com/simple/ flask

或者创建 ~/.pip/pip.conf ,写入

[global]
index-url = https://pypi.douban.com/simple/

2018-06-24 python , linux , notes

电子书

Google+

最近文章

  • Java 查漏补缺之泛型 简而言之,泛型使类型(类和接口)在定义类,接口和方法时成为参数。类型参数提供了一种简便的方法,使得不同的输入类型可以使用相同的代码。
  • Spring 相关资源 收集了一些官方的非官方的学习资源。
  • Java 查漏补缺之 Class.isAssignableFrom() 和 instanceof 区别 Class.isAssignableFrom() 是用来判断一个类 Class1 和另一个类 Class2 是否相同或是另一个类的超类 superclass 或接口 superinterface。
  • maven plugins maven-compiler-plugin pom 文件定义,默认的 source 和 target 都是 1.5。
  • phpMyAdmin 登录无反应问题记录 今天遇到一个神奇的错误,登录 phpMyAdmin 无论如何都登录不进去,开始还以为密码记错了,但是再输入错误密码时,会显示错误的提示