第18章 Java I/O系统
18.1 File 类

  18.1.1 目录列表器
  18.1.2 目录实用工具
  18.1.3 目录的检查及创建
18.2 输入和输出
在Java 1.0中类库的设计者限定于输入有关的类从InputStream继承;而与输出有关的所有类都应该从OutputStream继承

  18.2.1 InputStream类型

  18.2.2 OutputStream类型

18.3 添加属性和有用的接口
  18.3.1 通过FilterInputStream从InputStream读取数据

  18.3.2 通过FilterOutputStream向OutputStream写入

18.4 Reader和Writer》兼容Unicode与面向字符的I/O

  18.4.2 更改流的行为

  18.4.3 未发生变化的类

18.5 自我独立的类:RandomAccessFile》它适用于大小已知的记录组成的文件
18.6 I/O流的典型使用方式
  18.6.1 缓冲输入文件》BufferedReader
    BufferedReader in=new BufferedReader(new FileReader(filename));
  18.6.2 从内存输入》StringReader
    StringReader in=new StringReader(BufferedInputFile.read("xxx.txt"));
  18.6.3 格式化的内存输入》DataInputStream
    DataInputStream in=new DataInputStream(new ByteArrayInputStream("xxxxx.java").getBytes());
  18.6.4 基本的文件输出》FileWriter可以向文件中写入数据
    Printwriter out=new PrintWriter(new BufferedWriter(new FileWriter("xxxx.out")));
    文本文件的输出的快捷方式
      PrintWriter out=new PrintWriter("xxxx.out");
  18.6.5 存储和恢复数据》使用DataOutputStream写入数据,使用DataInputStream读取数据
    DataOutputStream out=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));
    DataInputStream in=new DataInputStream(new BufferedInputStream(new FileInputStream("data.txt")));
  18.6.6 读写随机访问文件》RandomAccessFile,可以利用seek()在文件中到处移动;其限制是必须知道文件的排版
  18.6.7 管道流》PipedInputStream,PipedOutputStream,PipedReader及PipedWriter

18.7 文件读写的实用工具
  18.7.2 读取二进制文件
18.8 标准I/O
  18.8.1 从标准输入中读取》System.in,System.out和System.err
  18.8.2 将System.out转换成PrintWriter》System.out是一个PrintStream,而PrintStream是一个OutputStream
    PrintWriter out=new PrintWriter(System.out,true);
  18.8.3 标准I/O重定向
18.9 进程控制》要想运行一个系统程序,需要向OSExecute.command()传递一个command字符串,它与在控制台中运行该程序键入的命令相同
18.10 新的I/O》java.nio.*
  FileChannel 以及java.nio.channels.Channels
  18.10.1 转换数据
  18.10.2 获取基本类型》ByteBuffer虽然只能保存字节类型数据,但是它具有从其所包含的字节中产生出不同基本类型值的方法
  18.10.3 视图缓冲期》例如:IntBuffer操作ByteBuffer中的int型数据

  18.10.4 用缓冲器操纵数据》想把一个字节数组写入到文件中,在nio类中,用ByteBuffer.wrap()把字节数组包装起来,然后用
    getChannel()方法在FileOutputStream上打开一个通道,接着将ByteBuffer的数据写到FileChannel中
  18.10.5 缓冲期的细节

18.10.6 内存映射文件
18.10.7 文件加锁

  1. public class FileLocking{
  2. public static void main(String[] args){
  3. FileOutputStream fos=new FileOutputStream("file.txt");
  4. FileLock fl=fos.getChannel().tryLock();
  5. if(fl!=null){
  6. System.out.println("Locked File");
  7. TimeUnit.MILLISECONDS.sleep(100);
  8. fl.release();
  9. System.out.println("Release File");
  10. }
  11. fos.close();
  12. }
  13. }

18.11 压缩

18.11.1 用GZIP进行简单压缩

  1. public class GZIPcompress{
  2. public static void main(String[] args){
  3. BufferedReader in=new BufferedReader(new FileReader("xxxx.txt"));
  4. BufferedOutputStream out=new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("text.gz")));
  5. int c;
  6. while((c=in.read())!=-1){
  7. out.write(c);
  8. }
  9. in.close();
  10. out.close();
  11. BufferedReader in2=new BufferedReader(new InputStreamReader(new GZIPInputStream(
  12. new FileInputStream("test.gz"))));
  13. String s;
  14. while((s=in2.readLine())!=null)
  15. System.out.println(s);
  16. }
  17. }

  18.11.2 用Zip进行多文件保存
  GZIP或Zip库的使用并不仅仅局限于文件————它可以压缩任何东西,包括需要通过网络发送的数据
  18.11.3 Java档案文件
18.12 对象序列化
  要序列化一个对象:创建某些OutputStream对象》封装到一个ObjectOutputSteam对象内》
调用writeObject()即可序列化》将其发送给OutputStream(因为对象序列化是基于字节的,
因要使用InputSteam和OutputSteam继承层次结构);要反向进行该过程,需要将一个InputStream
封装在ObjectInputStream内,然后调用readObject()

  18.12.1 寻找类
  18.12.2 序列化控制》Externalizable继承了Serializable,同时新增了writeExternal和readExternal
  18.12.3 使用“持久性”

18.13 XML
18.14 Preferences
  Preferences API与对象序列化相比,前者与对象持久性更密切,因为它可以自动存储和读取信息。不过,它只能用于小的,
受限的数据集合————只能存储基本类型和字符串,并且每个字符串的存储长度不能超过8K
Preferences用户存储和读取用户的偏好(preferences)以及程序配置项的设置

第19章 枚举类型
19.1 基本enum特性
  19.1.1 将静态导入用于enum

  1.      public enum Spiciness{
  2. NOT,MILD,MEDIUM,HOT,FLAMING
  3. }

19.2 向enum中添加新方法》除了不能继承自一个enum之外,基本上可以将enum看作一个常规的类
注意:如果打算在一个enum中定义自己的方法,那么必须在enum实例序列的最后添加一个分号。同时,Java中必须先定义enum实例。
如果在定义enum实例之前定义了任何方法或属性,那么在编译时就会得到错误信息.

  1. public enum OzWithc{
  2. WEST("Miss Gulch,aka the Wicked witch of the west"),
  3. NOTRH("Glinda,the good witch of the north"),
  4. EAST("Wicked witch of the east"),
  5. SOUTH("Good by inference,but missing");
  6.  
  7. private String description;
  8. private OzWithc(String description){
  9. this.description=description;
  10. }
  11.  
  12. public String getDescription(){
  13. return description;
  14. }
  15. public static void main(String[] args){
  16.  
  17. }
  18. }

  19.2.1 覆盖enum的方法
19.4 values的神秘之处
19.5 实现,而非继承

  1. enum CartoonCharacter implements Generator<CartoonCharacter>{
  2. SLAPPY,SPANKY,PUNCHY,SILLY,BOUNCY,NUTTY,BOB;
  3. private Random rand=new Random(47);
  4. public CartoonCharacter next(){
  5. return values()[rand.nextInt(values().length]
  6. }
  7. }

19.6 随机选取

19.7 使用接口组织枚举
有时想将枚举元素分类组织,可以在一个接口内部创建实现该接口的枚举,以此将元素进行分组

  1. public interface Food{
  2. enum Appetizer implements Food{
  3. SALAD,SOUP,SPRING;
  4. }
  5. enum MainCourse implements Food{
  6. LASAGNE,BURRITO,PAD,LENTILS,HUMMOUS;
  7. }
  8. ....
  9. }

19.8 使用EnumSet替代标志
19.9 使用EnumMap
19.10 常量相关的方法

  1. public enum ConstantSpecificMethod{
  2. DATE_TIME{
  3. String getInfo(){
  4. return DateFormat.getDateInstance().format(new Date());
  5. }
  6. },
  7. CLASSPATH{
  8. String getInfo(){
  9. return System.getProperty("CLASSPATH");
  10. }
  11. },
  12. VERSION{
  13. String getInfo(){
  14. return System.getProperty("java.version");
  15. }
  16. }
  17. abstract String getInfo();
  18. public static void main(String[] args){
  19. for(ConstantSpecificMethod csm: values){
  20. System.out.println(csm.getInfo());
  21. }
  22. }
  23. }

19.11 多路分发

第20章 注解
20.1 基本语法
  20.1.1 定义注解
注解和其他任何Java接口一样,注解也会编译成class文件

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Test()
  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface UseCase{
  4. public int id();
  5. public String description() default "no description";
  6. }
  1. public class PasswordUtils{
  2. @UseCase(id=47,description="Password must contain at least one numeric")
  3. public boolean validatePassword(String password){
  4. return (password.matches("\\w*\\d\\w*"));
  5. }
  6.  
  7. @UseCase(id=48)
  8. public String encrytPassword(String password){
  9. return new StringBuilder(password).reverse().toString();
  10. }
  11. }

  20.2.1 注解元素
  注解元素可用的类型如下:
    所有的基本类型(int,float,boolean等)
    String
    Class
    enum
    Annotation
    以上类型的数组

  20.2.2 默认值限制
    1.元素不能有不确定的值,也就是说元素必须要么具有默认值,要么在使用注解时提供元素的值
    2.对于非基本类型的元素,无论是在源代码中声明时,或者是在注解接口中定义默认值时,都不能以null作为其值
  20.2.3 生成外部文件
  20.2.4 注解不支持继承》不能使用关键字extends来继承某个@interface
20.4 将观察者模式用于apt
20.5 基于注解的单元测试

第21章 并发
  21.1 并发的多面性
并发编程令人困惑的一个主要原因就是:使用并发时需要解决的问题有多个,而实现并发的方式也有多种,并
且在这两者之间没有明显的映射关系(而且通常只具有模糊的界限)
    21.1.1 更快的执行
    实现并发最直接的方式是在操作系统级别使用进程.进程是运行在它自己的地址空间内的自包容的程序
编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对这些资源使用,以使得这些资源不会同时被多个任务访问
  21.2 基本的线程机制

  线程模型为编程带来了便利,它简化了咋单一程序中同时交织在一起的多个操作的处理。在使用线程时,CPU将轮流给每个任务
分配其占用的时间[当系统使用时间切片机制时,情况确实如此(Windows)。Solaris使用了FIFO并发模型:除非有高优先级的线程被
唤醒,否则当前线程将一直运行,直至它被阻塞或终止.这意味着具有相同优先级的其他线程在当前线程放弃处理器之前,将不会运行]
  21.2.1 定义任务
  线程可以驱动任务,而描述任务的方式由Runnable接口提供,只需要实现Runnable接口并编写run()方法

  1. public class LiftOff implements Runnable{
  2. protected int countDown=0;
  3. private static int taskCount=0;
  4. private final int id=taskCount++;
  5. public LiftOff(){}
  6. public LiftOff(int countDown){
  7. this.countDown=countDown;
  8. }
  9. public String status{
  10. return "#"+id+"("+(countDown>0?countDown:"Liftoff!")+")";
  11. }
  12.  
  13. public void run(){
  14. while(countDown-->0){
  15. System.out.println(status);
  16. Thread.yield();
  17. }
  18. }
  19. }

    在run()中对静态方法Thread.yield()的调用是对线程调度器(Java线程机制的一部分,可以将CPU从一个线程转移给另一个线程)
的一种建议,它在声明:“我已经执行完生命周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机”。这完全是
选择性的,但是这里使用它是因为它会在这些示例中产生更加有趣的输出
当从Runnable继承出来的类,它必须具有run()方法,但是这个方法并无特殊之处---它无任何内在的线程能力。要实现线程行为,
必须显式地将一个任务附着在线程上。
  21.2.2 Thread类
    将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器

  1. public class BasicThreads{
  2. public static void main(String[] args){
  3. Thread t=new Thread(new LiftOff());
  4. t.start();
  5. }
  6. }

  21.2.3 使用Executor
  Java SE5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化并发编程;Executor在客户端
和任务执行之间提供了一个间接层,与客户端直接执行任务不同,这个中介对象将执行任务.Executor允许你管理异步任务的执行
,而无须显示的管理线程的生命周期.Executor在Java SE5/6中是启动任务的优选方法;ExecutorService对象是使用静态的Executor
方法创建的,这个方法确定其Executor类型

  1. public class ThreadDemo {
  2. public static void main(String[] args) {
  3. ExecutorService executorService=Executors.newCachedThreadPool();
  4. //ExecutorService executorService=Executors.newFixedThreadPool(5);//有限的线程集
  5. for (int i = 0; i <5; i++) {
  6. executorService.execute(new LiftOff());
  7. }
  8. executorService.shutdown();
  9. }
  10. }

  CachedThreadPool在程序执行过程中通常会创建于所需数量相同的线程,然后在它回收旧线程时停止创建新的线程,因此它是合理
的Executor的首选.只有当这种方式会引发问题时,才需要切换到FixedThreadPool
SingleThreadExecutor相当于线程数量为1的FixedThreadPool,如果想SingleThreadExecutor提交了多个任务,那么这些任务将排
队,每个任务都会在下一个任务开始之前结束,所有的任务将使用相同的线程.

  21.2.4 从任务中产生返回值
    Runnable不返回任何值.如果希望在任务完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE5中引
入的Callable是一个具有类型参数的泛型,它的类型参数表示是从方法call()[而不是run()]中返回的值,并且必须使用ExecutorService.submit()
方法调用

  1. class TaskWithResult implements Callable<String>{
  2. private int id;
  3. public TaskWithResult(int id){
  4. this.id=id;
  5. }
  6. public String call(){
  7. return "result of TaskWithResult "+id;
  8. }
  9. }
  10.  
  11. public class CallableDemo{
  12. public static void main(String[] args){
  13. ExecutorService exec=Executors.newCachedThreadPool();
  14. ArrayList<Future<String>> results=new ArrayList<Future<String>>;
  15. for(Future<String> fs:results){
  16. //get() blocks untils completic
  17. System.out.println(fs.get());
  18. exec.shutwodn();
  19. }
  20. }
  21. }

21.2.5 休眠
  影响任务行为的一种简单方法是调用sleep()

  1. public class SleepingTask extends LiftOff{
  2. public void run(){
  3. try{
  4. while(countDown-->0){
  5. System.out.print(status());
  6. TimeUnit.MILLISECONDS.sleep(100);
  7. }
  8. }catch (InterruptedException e){
  9. System.err.println('Interrupted..');
  10. }
  11. }
  12. public static void main(String[] args){
  13. ExecutorService exec=Executors.newCachedThreadPool();
  14. for(int i=0;i<5;i++){
  15. exec.executor(new SleepingTask());
  16. }
  17. exec.shutdown();
  18. }
  19. }

  21.2.6优先级》Thread.currentThread().setPriority() ;getPriority();
  尽管JDK有10个优先级,但是与多数操作系统都不能很好的映射; Windows有7个优先级;Sun的Solaris有
2的31次方个优先级;唯一可以移植的是MAX_PRIORITY,NORM_PRIORITY和MIN_PRIORITY

  21.2.7 让步》通过调用yield()方法做出一个让步暗示
  21.2.8 后台线程》所谓的后台线程是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并
不属于程序中不可以或缺的部分

  1. public class SimpleDaemon implements Runnable{
  2. public void run(){
  3. try{
  4. while(true){
  5. TimeUnit.MILLISECONDS.sleep(100);
  6. print(Thread.currentThread()+" "+this);
  7. }
  8. }catch(InterruptedException e){
  9. print("sleep() interrupted")
  10. }
  11. }
  12. public static void main(String[] args){
  13. for(int i=0;i<10;i++){
  14. Thread daemon=new Thread(new SimpleDaemon());
  15. daemon.setDaemon(true);//must call before start
  16. daemon.start();
  17. }
  18. }
  19. }

  21.2.6优先级》Thread.currentThread().setPriority() ;getPriority();
  尽管JDK有10个优先级,但是与多数操作系统都不能很好的映射; Windows有7个优先级;Sun的Solaris有
2的31次方个优先级;唯一可以移植的是MAX_PRIORITY,NORM_PRIORITY和MIN_PRIORITY

  21.2.7 让步》通过调用yield()方法做出一个让步暗示
  21.2.8 后台线程》所谓的后台线程是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并
不属于程序中不可以或缺的部分
  21.2.9 编码的变体
  21.2.10 术语》Java的线程机制基于来自C的低级的p线程方式
  21.2.11 加入一个线程》一个线程在另一个线程之上调用join()方法,其效果是等待一段时间知道第二个线程
  结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(
t.isAlive()返回为假)
  21.2.12创建有响应的用户界面
  21.2.13 线程组》用来自Joshua Bloch[曾是Sun公司的软件架构师]的话来总结:
“最好把线程组看成是一次不成功的尝试,你只要忽略它就好”
  21.2.14捕获异常

21.3 共享受限资源
  可以把单线程程序当作问题域求解的单一实体,每次只能做一件事情
  21.3.1 不正确的访问资源
  21.3.2 解决共享资源竞争
  防止多个多个任务同时访问相同的资源的方法就是当资源被一个任务访问时,在其上加锁
只有该资源解锁时才能让其他任务访问它
  基本上所有的并发模式在解决线程冲突时都是采用序列化访问共享资源的方案,也就是在
给定时刻只允许一个任务访问共享资源
  Java提供关键字synchronized为防止资源冲突提供了内置支持,当任务要执行被synchronized
关键字保护的代码块时,它将检查锁是否可用,然后获取锁,执行代码,释放锁
  注意,在使用并发时,将域设置为private是非常重要的,否则,synchronized关键字就不能防止
其他任务直接访问域,这样就会产生冲突
  一个任务可以多次访问获得对象的锁;每当一个任务首次在这个对象上获得锁后,在没有解锁
之前有调用了这个对象上的另一个方法,那么锁计数就会增加
针对每个类,也有一个锁,所以synchronized static 方法可以在类范围内防止对static数据的并发访问

  应该在什么时候使用同步呢?可以运用Brian的同步规则
    “如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程
  写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器同步”

  如果类中有超过一个方法在处理临界数据,那么必须同步所有相关的方法,每个访问临界共享资源的方法都
必须被同步,否则它们就不会正确的工作

Thread.yield() / java.util.concurrent.locks / synchronized

  21.3.3 原子性与易变性》用原子性替代同步是很危险的想法
  原子性可以应用于long和double之外的所有基本类型之上
  volatile 关键字
  使用volatile而不是synchronized的唯一安全情况就是类中只有一个可变的域.再次提醒,你的
第一个选择应该使用synchronized关键字,这是最安全的方式,尝试其他任何方式都是有风险的

  21.3.4 原子类
  21.3.5 临界区
  如果只希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法,通过这种方式分
离出来的代码被称为临界区,它也可以使用synchronized建立
  synchronized(syncObject){
    ...
  }
  21.3.6 在其他对象上同步
  21.3.7 线程本地存储》线程本地存储就是为相同的变量在每个不同的线程上创建不同的存储

Java编程思想(18~22)的更多相关文章

  1. [Java编程思想-学习笔记]第3章 操作符

    3.1  更简单的打印语句 学习编程语言的通许遇到的第一个程序无非打印"Hello, world"了,然而在Java中要写成 System.out.println("He ...

  2. Java编程思想重点笔记(Java开发必看)

    Java编程思想重点笔记(Java开发必看)   Java编程思想,Java学习必读经典,不管是初学者还是大牛都值得一读,这里总结书中的重点知识,这些知识不仅经常出现在各大知名公司的笔试面试过程中,而 ...

  3. java编程思想

    Java编程思想,Java学习必读经典,不管是初学者还是大牛都值得一读,这里总结书中的重点知识,这些知识不仅经常出现在各大知名公司的笔试面试过程中,而且在大型项目开发中也是常用的知识,既有简单的概念理 ...

  4. 《Java编程思想》读书笔记

    前言 这个月一直没更新,就是一直在读这本<Java编程思想>,这本书可以在Java业界被传神的一本书,无论谁谈起这本书都说好,不管这个人是否真的读过这本书,都说啊,这本书很好.然后再看这边 ...

  5. 《Java编程思想第四版完整中文高清版.pdf》-笔记

    D.2.1 安插自己的测试代码 插入下述“显式”计时代码,对程序进行评测: long start = System.currentTimeMillis(); // 要计时的运算代码放在这儿 long ...

  6. 《Java编程思想第四版》附录 B 对比 C++和 Java

    <Java编程思想第四版完整中文高清版.pdf>-笔记 附录 B 对比 C++和 Java “作为一名 C++程序员,我们早已掌握了面向对象程序设计的基本概念,而且 Java 的语法无疑是 ...

  7. 《Java编程思想》阅读笔记二

    Java编程思想 这是一个通过对<Java编程思想>(Think in java)进行阅读同时对java内容查漏补缺的系列.一些基础的知识不会被罗列出来,这里只会列出一些程序员经常会忽略或 ...

  8. java编程思想 内容总结

    Java编程思想重点笔记(Java开发必看) Java编程思想,Java学习必读经典,不管是初学者还是大牛都值得一读,这里总结书中的重点知识,这些知识不仅经常出现在各大知名公司的笔试面 试过程中,而且 ...

  9. Java编程思想(第4版) 中文清晰PDF完整版

    Java编程思想(第4版) 中文清晰PDF完整版 [日期:2014-08-11] 来源:Linux社区  作者:Linux [字体:大 中 小]     <Java编程思想>这本书赢得了全 ...

随机推荐

  1. Win8.1离线安装.Net Framework 3.5

     在线安装太慢了! 只要一个命令搞掂 不希望使用Internet连接,可以使用DISM (部署映像服务和管理工具)离线部署 .NET Framework 3.5   1. Win+X选择命令提示符(管 ...

  2. frame框架及其实例

    框架概念 : 谓框架便是网页画面分成几个框窗,同时取得多个 URL.只需要 <FRAMESET> <FRAME> 即可,面所有框架标记需要放在一个总起的 html 档,这个档案 ...

  3. 如何禁止同IP站点查询和同IP站点查询的原理分析 Robots.txt屏蔽BINGBOT

    很多站长工具中都有“同IP站点查询”.“IP反查域名”这种服务不少人都不知道是什么原理,其实这些服务几乎都是用BING(以前的LIVE)来实现 的,BING有个特别功能 BING抓取页面时会把站点的I ...

  4. 26最小公倍数 lowest common multiple

    题目描述 正整数A和正整数B 的最小公倍数是指 能被A和B整除的最小的正整数值,设计一个算法,求输入A和B的最小公倍数. 输入描述:输入两个正整数A和B. 输出描述:输出A和B的最小公倍数. 输入例子 ...

  5. 分区容量大于16TB的格式化

    File systems do have limits. Thats no surprise. ext3 had a limit at 16 TB file system size. If you n ...

  6. 本地filezilla&amp;servervsftp搭配使用

    环境:本地ubuntu系统&serverubuntu系统 本地安装filezilla  apt-get install filezilla '安装filezilla filezilla '执行 ...

  7. vagrant 介绍,安装与使用

    可以帮你统一团队成员的开发环境.如果你或者你的伙伴创建了一个Vagrantfile,那么你只需要执行vagrant up就行了,所有的软件都会安装并且配置好.团队成员可以通过相同的Vagrantfil ...

  8. 常用yum命令小结

    基于rpm的软件包管理器 yum,是Yellow dog Updater, Modified的简称,是一种软件包管理器.它能够从指定的服务器自动下载RPM包并安装,可以自动处理依赖性关系,并且一次安装 ...

  9. ASP.NET RemoteAttribute远程验证更新问题

    create时使用remote特性没有任何问题, update时,问题就大了,验证唯一性时需要排除自身,如果使用这个特性将无法正确的验证. 改进思路:将自动生成的标签属性改为手写,,并在url上面加上 ...

  10. 【Android】第三方库使用的问题集

    Google/百度地图Key的获取 百度地图UnsatisfiedLinkError错误 async-http-client中的FATAL EXCEPTION Google/百度地图Key的获取 无论 ...