零碎笔记(一)

       TCP的发送缓冲区中的数据,如果收不到接收方的ACK就不会删除,导致发送缓冲区溢出。如果接收方的缓冲区满了,收到数据后会不会向发送方发ACK呢?
答案:
    1. 只要收到了包,就会ACK。
    2. TCP在ACK的同时会带有window大小值,表示这边能接受的数据量。发送方会根据这个调整数据量。
    3. 接收方缓冲区满时,回给发送方的window值就是0。
    4. 发送方看到window为0的包,会启动一个定时器,隔一段时间发一个包试探。
    5. 一旦接收方缓冲区有足够空间了,就会给window赋上非0值。发送方就又开始发送了。



       程序中的所有线程共享一个方法区,所以访问方法区信息的方法必须是线程安全的。如果有两个线程都去加载一个叫Lava的类,那只能由一个线程被容许去加载这个类,另一个必须等待。



       wait()等待对象锁和synchronized等待对象锁的区别, wait()等待时,线程是在阻塞状态,所以需要notify或者notifyAll唤醒使线程状态变为就绪状态才能继续竞争对象锁, 而synchronized等待时, 线程本身就在就绪状态, 所以线程可以直接竞争对象锁(线程可能一直在循环查询对象锁是否可用)。还有一点,线程是获取了锁之后才能调用wait(),从而释放锁并阻塞;不能凭空调用wait()。




       一般情况下,对于基本类型用==,对于引用类型用equals()。对于引用类型如果可以用==代替equals(),可以大大提高效率。
举例:如何判断一个Class是否指定的类型
如果不用instanceof该怎么处理呢?给定一个Class c,判断是否为String类的Class
方法1:boolean b=c.getName().equals(String.class.getName());
方法2:boolean b=(c==String.class);
第二种方法至少快100倍,甚至1000倍或者10000倍,因为用==几乎不用时间。对于可以使用==的比较,不要使用equals();一般单例类的实例可以使用==,比如枚举类。


try中抛出异常,但如果finally 中也抛出异常的话,try中的异常信息会被忽略掉。例如:
1.异常信息丢失案例
public void test_1(){
    //异常信息丢失案例
    try {
        Integer.parseInt("hello");
    } catch (NumberFormatException e) {
        log.info("{excetion:}",e);
        throw  e; //这个异常被下面的掩盖了。
    }finally{
        int a = 12 / 0; //expected=ArithmeticException.class 这里的异常
    }
}
上面的测试抛出的异常不是try中的NumberFormatException,而是finally 中的ArithmeticException。即try中的异常信息被丢弃。

2. 解决方法之一:
public void test_2(){
    //异常信息丢失案例:解决方法
    try {
        Integer.parseInt("hello");
    } catch (NumberFormatException e) {
        log.info("{excetion:}",e);
        throw e; //这个异常被下面的掩盖了。
    }finally{
        try {
            int a = 12 / 0;
        } catch (Exception e) {
            // 忽略此处异常,上面的主要根源异常就可以跑出去了
        }
    }
}
代码抛出的异常是NumberFormatException,就是try中异常是根源,finally中的异常就丢弃了。

3.解决方法之二:
public void test_3(){
    RuntimeException throwable = null;  
    try {  
        Integer.parseInt("hello");  
    } catch (NumberFormatException e) {  
         throwable = e;  
    } finally {  
           try {  
               int a = 10 / 0;  
           } catch (ArithmeticException e) {  
                if (throwable == null) {  
                       throwable = e;  
                } else {  
                       throwable.addSuppressed(e);  
                }  
           }  
    }  
    if (throwable != null) {  
         log.error("", throwable);  
         throw throwable;  
    }  
}
利用addSuppressed 方法,把异常堆栈信息保留起来,然后再带出去。


@ResponseBody,在@Controller 类方法中可以让字符串直接返回内容。
其返回处理的类是org.springframework.http.converter.StringHttpMessageConverter,此类默认编码是:
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
想自定义返回编码,需配置一下:
<mvc:annotation-driven>  
    <mvc:message-converters register-defaults="true">  
         <bean class="org.springframework.http.converter.StringHttpMessageConverter">  
              <constructor-arg value="UTF-8" />  
         </bean>
    </mvc:message-converters>
</mvc:annotation-driven>


       早在Java5.x的时候,Java API为开发人员提供了一个Closeable接口。该接口中包含一个close()方法,允许所有可回收资源的类型对其进行重写实现。7.x版本中几乎所有的资源类型都实现了Closeable接口,并重写了close()方法。也就是说所有可回收的系统资源,将再不必每次使用完后调用close()方法进行资源回收,这一切全部交接给自动资源管理器去做即可。
       例如Reader资源类型继承Closeable接口实现资源自动管理:
              public abstract class Reader implements Readable, Closeable  
       当然如果需要在程序中使用自动资源管理,还需要使用API提供的新语法支持,这类语法包含在try语句块内部。7.x确实允许try使用表达式的语法方式实现自动资源管理,但仅限于资源类型。
       使用try表达式实现自动资源管理:
    try(BufferedReader reader = new BufferedReader(new FileReader("路径"));) {
        //...  
    } catch(Exception e) {
        e.printStackTrace();
    }


       声明二进制,早在Java7.x版本之前,开发人员只能够定义十进制、八进制、十六进制等字面值。但是现在完全可以使用“0b”字符为前缀定义二进制字面值。
       定义二进制字面值: int test = 0b010101;   
       需要提示的是,虽然可以直接在程序中定义二进制字面值。但是在程序运算时,仍然会将其转换成十进制展开运算和输出。


       Java的文件系统主要由java.io及java.nio两个包内的组件构成。早在Java7.x之前,文件的操作比较繁琐。比如需要编写一个程序,这个程序的功能仅仅只是拷贝文件后进行粘贴。
       使用Java File API操作文件核心示例:
    /* 复制目标数据源数据 */  
    BufferedInputStream reader = new BufferedInputStream(  
            new FileInputStream(COPYFILEPATH));  
    byte[] content = new byte[reader.available()];  
    reader.read(content);  
    
    /* 将复制数据粘贴至新目录 */  
    BufferedOutputStream write = new BufferedOutputStream(  
            new FileOutputStream(PASTEFILEPATH));  
    write.write(content);  
       通过上述程序示例可以看出,仅仅只是编写一个简单的文件复制粘贴逻辑,代码量都大得惊人。而Java7.x对文件系统的一次全新改变。
       Java7.x推出了全新的NIO.2 API以此改变针对文件管理的不便,使得在java.nio.file包下使用Path、Paths、Files、WatchService、FileSystem等常用类型可以很好的简化开发人员对文件管理的编码工作。
       Path接口的某些功能其实可以和java.io包下的File类型等价,当然这些功能仅限于只读操作。在实际开发过程中,可以联用Path接口和Paths类型,从而获取文件的一系列上下文信息。
Path接口常用方法如下:
方法名称    方法返回类型    方法描述
getNameCount()    int    获取当前文件节点数
getFileName()    java.nio.file.Path    获取当前文件名称
getRoot()    java.nio.file.Path    获取当前文件根目录
getParent()    java.nio.file.Path    获取当前文件上级关联目录

联用Path接口和Paths类型获取文件信息:
    public void testFile() {  
        Path path = Paths.get("路径:/文件");  
        System.out.println("文件节点数:" + path.getNameCount());     
        System.out.println("文件名称:" + path.getFileName());     
        System.out.println("文件根目录:" + path.getRoot());     
        System.out.println("文件上级关联目录:" + path.getParent());     
    }  
通过上述示例可以看出,联用Path接口和Paths类型可以很方便的访问到目标文件的上下文信息。当然这些操作全都是只读的,如果想对文件进行其它非只读操作,比如文件的创建、修改、删除等操作,则可以使用Files类型进行操作。
Files类型常用方法如下:
方法名称    方法返回类型    方法描述
createFile()    java.nio.file.Path    在指定的目标目录创建新文件
delete()    void    删除指定目标路径的文件或文件夹
copy()    java.nio.file.Path    将指定目标路径的文件拷贝到另一个文件中
move()    java.nio.file.Path    将指定目标路径的文件转移到其他路径下,并删除源文件

使用Files类型复制、粘贴文件示例:
    Files.copy(Paths.get("路径:/源文件"), Paths.get("路径:/新文件"));  
可以看出,使用Files类型来管理文件,相对于传统的I/O方式来说更加方便和简单。因为具体的操作实现将全部移交给NIO.2 API,开发人员则无需关注。


在Java中,使用反射的方法加载类的具体代码是:Class<?> cls = Class.forName("package.ClassName");
但是,如果需要加载的是一个内部类的话,如果使用这种加载方式,会抛出异常:
Class<?> cls = Class.forName("package.ClassName.InnerClass"); // 如果使用这样的代码,会抛出ClassNotFoundException
对于内部类,需要像下面这样写代码:Class<?> cls = Class.forName("package.ClassName$InnerClass");


非spring管理的类实例如何托管给spring容器管理
    public void test_contextBeanRegistrationOfExistingObjects() {  
        ApplicationContextBeanRegistrationOfExistingObjects bean = null;  
        try {  
            bean = (ApplicationContextBeanRegistrationOfExistingObjects) context.getBean(ApplicationContextBeanRegistrationOfExistingObjects.class);  
        } catch (BeansException e) {  
        }  
        context.getBeanFactory().registerSingleton("applicationContextBeanRegistrationOfExistingObjects", new ApplicationContextBeanRegistrationOfExistingObjects());  
        bean = (ApplicationContextBeanRegistrationOfExistingObjects) context.getBean(ApplicationContextBeanRegistrationOfExistingObjects.class);  
        System.out.println(bean);  
    } 


SLF4J 1.6.0以前的版本,如果打印异常堆栈信息,必须用log.error(String msg, Throwable t)如果msg含有变量,一般用String.format方法格式化msg.
如果用log.error(String format, Object... arguments)等其它方法,异常堆栈信息会丢失.
幸好,SLF4J 1.6.0以后的版本对异常信息改进了.Log.error(String format, Object... arguments) 这个方法也会打印异常堆栈信息,只不过规定throwable对象必须为最后一个参数.如果不遵守这个规定,异常堆栈信息不会log出来.
    String s = "Hello world";
    try {
      Integer i = Integer.valueOf(s);
    } catch (NumberFormatException e) {
      logger.error("Failed to format {}", s, e);
    }


Stream<String> stream = list.stream().filter(p -> p.length() > 3);  
String[] arr = stream.toArray(String[]::new);


依赖注入
       在Java里面,最典型的例子是Spring框架。它可以使用基于注解的注入或基于XML配置的注入。如果想使用XML配置,很重要的事情是不要过度使用Spring的这种基于XML的配置形式。在这个XML中绝不应该有逻辑和控制结构,它仅仅是依赖注入。
       使用的比较好的Spring例子是Google和Square的Dagger库以及Google的Guice。它们都没有使用Spring的XML配置文件,而是把注入的逻辑写在了注解和代码里面。

避免空值
       尽可能的避免使用null。不要返回集合时使用null,而应当返回一个空的集合。如果将要使用null,可以考虑使用 @Nullable 的注解。如果使用的是Java 8,可以选择更好的 Optional 类。如果一个值可能存在也可能不存在,可以把它包装为一个 Optional 类.



       集合类型,只要有可能,请使用Guava的 ImmutableMap、ImmutableList 或 ImmutableSet 类。这些已存在的建造者可以让你通过调用它们的 build 方法动态的建造不可变的集合。


Apache Commons项目提供了许多有用的库。
      Commons Codec提供了许多关于Base64和16进制字符串的编码和解码方法。
      Commons Lang提供了String类的创建、操作及字符集等一系列工具方法。
      Commons IO拥有所有文件操作相关的方法。它有 FileUtils.copyDirectory、FileUtils.writeStringToFile、IOUtils.readLines等方法。


在需要迭代键值对形式的Map时一定要用 entrySet() 方法。


使用EnumSet或EnumMap
       在某些情况下,比如在使用配置map时,可能会预先知道保存在map中键值。如果这个键值非常小,就应该考虑使用 EnumSet 或 EnumMap,而并非使用常用的HashSet或 HashMap。下面的代码给出了很清楚的解释:
private transient Object[] vals;
 
public V put(K key, V value) {
    // ...
    int index = key.ordinal();
    vals[index] = maskNull(value);
    // ...
}
       上段代码的关键实现在于,用数组代替了哈希表。尤其是向map中插入新值时,所要做的仅仅是获得一个由编译器为每个枚举类型生成的常量序列号。如果有一个全局的map配置(例如只有一个实例),在增加访问速度的压力下,EnumMap会获得比HashMap更加杰出的表现。原因在于EnumMap使用的堆内存比HashMap要少一位(bit),而且HashMap要在每个键值上都要调用hashCode()方法和equals()方法。
       在用到类似枚举(enum-like)结构的键值时,就应该考虑将这些键值用声明为枚举类型,并将之作为 EnumMap 键。


得到当前方法的名字: String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();  


Java InputStream读取数据问题
原理讲解
1. 关于InputStream.read()
       在从数据流里读取数据时,经常用InputStream.read()方法。这个方法是从流里每次只读取读取一个字节,效率会非常低。更好的方法是用InputStream.read(byte[] b)或者InputStream.read(byte[] b,int off,int len)方法,一次读取多个字节。

2. 关于InputStream类的available()方法
       要一次读取多个字节时,经常用到InputStream.available()方法,这个方法可以在读写操作前先得知数据流里有多少个字节可以读取。需要注意的是,如果这个方法用在从本地文件读取数据时,一般不会遇到问题,但如果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,对方明明发来了1000个字节,但是自己的程序调用available()方法却只得到900,或者100,甚至是0,感觉有点莫名其妙,怎么也找不到原因。其实,这是因为网络通讯往往是间断性的,一串字节往往分几批进行发送。本地程序调用available()方法有时得到0,这可能是对方还没有响应,也可能是对方已经响应了,但是数据还没有送达本地。对方发送了1000个字节给你,也许分成3批到达,这你就要调用3次available()方法才能将数据总数全部得到。
      如果这样写代码:
      int count = in.available();  
      byte[] b = new byte[count];  
      in.read(b);
      在进行网络操作时往往出错,因为你调用available()方法时,对发发送的数据可能还没有到达,你得到的count是0。
      需要改成这样:
    int count = 0;  
    while (count == 0) {  
       //count = in.available();  
       count=response.getEntity().getContentLength();//(HttpResponse response)  
    }  
    byte[] b = new byte[count];  
    in.read(b);  

3. 关于InputStream.read(byte[] b)和InputStream.read(byte[] b,int off,int len)这两个方法都是用来从流里读取多个字节的,有经验的程序员就会发现,这两个方法经常 读取不到自己想要读取的个数的字节。比如第一个方法,程序员往往希望程序能读取到b.length个字节,而实际情况是,系统往往读取不了这么多。仔细阅读Java的API说明就发现了,这个方法 并不保证能读取这么多个字节,它只能保证最多读取这么多个字节(最少1个)。因此,如果要让程序读取count个字节,最好用以下代码:
    byte[] bytes = new byte[count];  
    int readCount = 0; // 已经成功读取的字节的个数  
    while (readCount < count) {
        if(readCount == count) 
            break;
        readCount += in.read(bytes, readCount, count - readCount);  
    }  
用这段代码可以保证读取count个字节,除非中途遇到IO异常或者到了数据流的结尾(EOFException).



你可能感兴趣的:(零碎笔记(一))