Java超级大火锅

1、事务

事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败。

事务的4大特性:

ACID,指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

详见:http://blog.csdn.net/scythe666/article/details/51790655

2、悲观锁和乐观锁

秒懂乐观锁和悲观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

原文:http://blog.csdn.net/hongchangfirst/article/details/26004335

3、非阻塞同步算法与CAS(Compare and Swap)无锁算法

要实现无锁(lock-free)的非阻塞算法有多种实现方法,其中CAS(比较与交换,Compare and swap)是一种有名的无锁算法。CAS, CPU指令,在大多数处理器架构,包括IA32、Space中采用的都是CAS指令。

CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS是项乐观锁技术。

当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。CAS无锁算法的C实现如下:

int compare_and_swap (int* reg, int oldval, int newval)
{
  ATOMIC();
  int old_reg_val = *reg;
  if (old_reg_val == oldval)
     *reg = newval;
  END_ATOMIC();
  return old_reg_val;
}

CAS比较与交换的伪代码可以表示为:

do{
       备份旧数据;
       基于旧数据构造新数据;
}while(!CAS( 内存地址,备份的旧数据,新数据 ))  

(上图的解释:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。)

就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。容易看出 CAS 操作是基于共享数据不会被修改的假设,采用了类似于数据库的 commit-retry 的模式。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。

详见:http://www.cnblogs.com/Mainz/p/3546347.html?utm_source=tuicool&utm_medium=referral

4、JVM内存模型

JVM内存模型分区见下图:

(1)方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。对于习惯在HotSpot 虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价。

(2)堆区:Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。由于逃逸分析等分析或优化技术,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。

Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”

(3)栈区:与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。栈中主要存放一些基本类型的变量(,int, short, long, byte, float,double, boolean, char)和对象句柄。

(4)程序计数器:程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

(5)本地方法栈:本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。

详见:http://blog.csdn.net/scythe666/article/details/51700142

5、Java反射机制

什么是反射

一般在开发针对java语言相关的开发工具和框架时使用,比如根据某个类的函数名字,然后执行函数,实现类的动态调用!

反射实例:

package test;

import java.lang.reflect.*;

/**
 * 基类Animal
 */
class Animal {

   public Animal() {
   }

   public int location;
   protected int age;
   private int height;
   int length;

   public int getAge() {
      return age;
   }

   public void setAge(int age) {
      this.age = age;
   }

   private int getHeight() {
      return height;
   }

   public void setHeight(int height) {
      this.height = height;
   }

}

/**
 * 子类 —— Dog
 */
class Dog extends Animal {

   public Dog() {

   }

   public String color;
   protected String name;
   private String type;
   String fur;

}

public class test {
    public static void main(String[] args) {
        System.out.println("Hello World!");

        testRefect();
    }

    private static void testRefect() {
        try {
            //返回类中的任何可见性的属性(不包括基类)
            System.out.println("Declared fileds: ");
            Field[] fields = Dog.class.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                System.out.println(fields[i].getName());
            }

            //获得类中指定的public属性(包括基类)
            System.out.println("public fields: ");
            fields = Dog.class.getFields();
            for (int i = 0; i < fields.length; i++) {
                System.out.println(fields[i].getName());
            }

            //getMethod()只能调用共有方法,不能反射调用私有方法
            Dog dog = new Dog();
            dog.setAge(1);
            Method method1 = dog.getClass().getMethod("getAge", null);
            Object value = method1.invoke(dog);
            System.out.println("age: " + value);

            //调用基类的private类型方法

         /**先实例化一个Animal的对象 */
         Animal animal = new Animal();
         Class a = animal.getClass();
         //Animal中getHeight方法是私有方法,只能使用getDeclaredMethod
         Method method2 = a.getDeclaredMethod("getHeight", null);
         method2.setAccessible(true);

         //java反射无法传入基本类型变量,可以通过如下形式
         int param = 12;
         Class[] argsClass = new Class[] { int.class };
         //Animal中getHeight方法是共有方法,可以使用getMethod
         Method method3 = a.getMethod("setHeight", argsClass);
         method3.invoke(animal, param);

         //Animal中height变量如果声明为static变量,这样在重新实例化一个Animal对象后调用getHeight(),还可以读到height的值
         int height = (Integer) method2.invoke(animal);
         System.out.println("height: " + height);

         /**不用先实例化一个Animal,直接通过反射来获得animal的class对象*/
         Class anotherAnimal = Class.forName("test.Animal");
            //Animal中getHeight方法是私有方法,只能使用getDeclaredMethod
            Method method4 = anotherAnimal.getDeclaredMethod("getHeight", null);
            method4.setAccessible(true);

            //java反射无法传入基本类型变量,可以通过如下形式
            int param2 = 15;
            Class[] argsClass2 = new Class[] { int.class };
            //Animal中setHeight方法是共有方法,可以使用getMethod
            Method method5 = anotherAnimal.getMethod("setHeight", argsClass2);
            method5.invoke(anotherAnimal.newInstance(), param2);

            //Animal中height变量必须声明为static变量,这样在重新实例化一个Animal对象后调用getHeight()才能读到height的值
            // 否则重新实例化一个新的Animal对象,读到的值为初始值
            int height2 = (Integer) method4.invoke(anotherAnimal.newInstance());
            System.out.println("height:" + height2);

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
         e.printStackTrace();
      } catch (InstantiationException e) {
         e.printStackTrace();
      }
   }

}

详见:http://blog.csdn.net/scythe666/article/details/51704809

6、类加载机制

深度好文:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html

类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。

类名.class是什么用法

反射相关知识,类名.class是获得这个类所对应的Class实例

从面向对象的角度上来看,一个类也是对象,它们是类这个类的对象,听起来有些抽象,但是在java中的实现就是所有的加载进来的类在虚拟机中都是一个java.lang.Class类的对象,而“类名.class”就是获得这个类的对象(在同一个ClassLoader中,类对象都是单例的)

当虚拟机载入某个class文件时,首先生成该class文件对应的类的Class对象,用这个对象描述类

7、Reactor和Proactor模式

Reactor读取操作:

(1)应用程序注册读就绪事件和相关联的事件处理器

(2)事件分离器等待事件的发生

(3)当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器

(4)事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理

写入操作类似于读取操作,只不过第一步注册的是写就绪事件。

Proactor读取操作:

(1)应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。

(2)事件分离器等待读取操作完成事件

(3)在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作(异步IO都是操作系统负责将数据读写到应用传递进来的缓冲区供应用程序操作,操作系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。

(4)事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。

Proactor中写入操作和读取操作,只不过感兴趣的事件是写入完成事件。

从上面可以看出,Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的

(1)Reactor中需要应用程序自己读取或者写入数据

(2)而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备。

综上所述,同步和异步是相对于应用和内核的交互方式而言的,同步 需要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。

详见:

http://blog.csdn.net/sshhbb/article/details/6331954

http://blog.csdn.net/scythe666/article/details/51811565

8、长连接 短连接

详情见:http://blog.chinaunix.net/uid-354915-id-3587924.html

(1)长连接就是Client和Server连接建立以后,不断开,直接进行报文的发送和接收。常用于点对点通讯

(2)短连接是每进行一次报文收发交易,才进行通讯连接。交易完毕后立即断开连接。此种方式常用于一点对多点通讯,比如多个Client连接一个Server。

9、线程池

在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

(1)newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

MyThread.java

package threadpool;

/**
 * Created by hupo.wh on 2016/7/2.
 */
public class WhThread extends Thread{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "正在执行...");
    }
}

TestSingleThreadExecutor.java

package threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by hupo.wh on 2016/7/3.
 */
public class TestSingleThreadExecutor {

    public static void main(String[] args) {

        //创建一个可重用固定线程数的线程池
        ExecutorService pool = Executors. newSingleThreadExecutor();

        //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
        Thread t1 = new WhThread();
        Thread t2 = new WhThread();
        Thread t3 = new WhThread();
        Thread t4 = new WhThread();
        Thread t5 = new WhThread();

        //将线程放入池中进行执行
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);

        //关闭线程池
        pool.shutdown();
    }
}

输出结果:

pool-1-thread-1正在执行…

pool-1-thread-1正在执行…

pool-1-thread-1正在执行…

pool-1-thread-1正在执行…

pool-1-thread-1正在执行…

(2)newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);

(3)newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newCachedThreadPool();

(4)newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

package threadpool;

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by hupo.wh on 2016/7/3.
 */
public class TestSingleThreadExecutor {

    public static void main(String[] args) {

        ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);

        exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间就触发异常
            @Override
            public void run() {

                System.out.println("================");
                throw new RuntimeException();
            }
        }, 1000, 5000, TimeUnit.MILLISECONDS);

        exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间打印系统时间,证明两者是互不影响的
            @Override
            public void run() {
                System.out.println(System.nanoTime());
            }
        }, 1000, 2000, TimeUnit.MILLISECONDS);
    }

}

ThreadPoolExecutor构造函数

jvm本身提供的concurrent并发包,提供了高性能稳定方便的线程池,可以直接使用。

ThreadPoolExecutor是核心类,都是由它与3种Queue结合衍生出来的。

BlockingQueue + LinkedBlockingQueue + SynchronousQueue

ThreadPoolExecutor的完整构造方法的签名是:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) .

corePoolSize - 池中所保存的线程数,包括空闲线程。

maximumPoolSize-池中允许的最大线程数。

keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

unit - keepAliveTime 参数的时间单位。

workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。

threadFactory - 执行程序创建新线程时使用的工厂。

handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

ThreadPoolExecutor是Executors类的底层实现。

在JDK帮助文档中,有如此一段话:

“强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程)

线程池实现原理

先从 BlockingQueue<Runnable> workQueue 这个入参开始说起。在JDK中,其实已经说得很清楚了,一共有三种类型的queue。

所有BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

(1)如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)

(2)如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。

(3)如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。

线程的状态有 new、runnable、running、waiting、timed_waiting、blocked、dead 一旦线程调用了start 方法,线程就转到Runnable 状态,注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还有优先级和调度问题。

排队策略

排队有三种通用策略:

(1)直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

(2)无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

(3)有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

详参:http://www.oschina.net/question/565065_86540

10、接口中的成员变量

用final定义出来的是常量.如public static final double CM=2.54;

如果楼主在接口定义里看到的是类似如上的定义的话,这是一个常量,可以通过接口继承直接取值.

最后,接口中声明的变量会被编译器和解释器自动当作public static final类型,也就是常量.所以在接口里声明的变量都要初始化

11、Java中的序列化与反序列化

详见:http://blog.csdn.net/scythe666/article/details/51718784

时间: 2024-10-09 21:05:27

Java超级大火锅的相关文章

【原创】用JAVA实现大文件上传及显示进度信息

用JAVA实现大文件上传及显示进度信息 ---解析HTTP MultiPart协议 一. 大文件上传基础描述: 各种WEB框架中,对于浏览器上传文件的请求,都有自己的处理对象负责对Http MultiPart协议内容进行解析,并供开发人员调用请求的表单内容. 比如: Spring 框架中使用类似CommonsMultipartFile对象处理表二进制文件信息. 而.NET 中使用HtmlInputFile/ HttpPostedFile对象处理二进制文件信息. 优点:使用框架内置对象可以很方便的

java读取 500M 以上文件,java读取大文件

java 读取txt,java读取大文件 设置缓存大小BUFFER_SIZE ,Config.tempdatafile是文件地址 来源博客http://yijianfengvip.blog.163.com/blog/static/175273432201191354043148/ package com.yjf.util;import java.io.File;import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;imp

大话重构连载16:超级大函数

事情总是这样的:当我们对一个遗留系统一忍再忍,再忍,忍,还要忍--终于积攒到某一天,实在忍无可忍了,拍案而起,不能再忍了,重构!!!事情就这样发生了.然而,在这时你突然发现,重构的工作千头万绪,真不知从何开始.堆积如山的问题此起彼伏,期望修改的设计思绪万千.这里有个想法,那里有个思路,什么都想做,却什么都做不了,真是脑子里一团乱麻.这时候,没有一个合理的步骤,清晰的计划,瞎干蛮干是十分危险的,它会为你的重构带来不可预期的未来.无数次的经验告诉我,不论是什么系统,采用什么架构,从分解大函数开始,肯

20亿与20亿表关联优化方法(超级大表与超级大表join优化方法)

记得5年前遇到一个SQL.就是一个简单的两表关联.SQL跑了几乎相同一天一夜,这两个表都非常巨大.每一个表都有几十个G.数据量每一个表有20多亿,表的字段也特别多. 相信大家也知道SQL慢在哪里了,单个进程的PGA 是绝对放不下几十个G的数据,这就会导致消耗大量temp tablespace,SQL慢就是慢在temp来回来回来回...的读写数据. 遇到这样的超级大表与超级大表怎么优化呢?这篇文章将告诉你答案. 首先创建2个測试表 t1,t2 数据来自dba_objects create tabl

Java查询大文本

但JAVA本身缺少相应的类库,需要硬编码才能实现结构化文件计算,代码复杂且可读性差,难以实现高效的并行处理. 使用免费的集算器可以弥补这一不足.集算器封装了丰富的结构化文件读写和游标计算函数,书写简单代码就能实现并行计算,并提供了易用的JDBC接口.JAVA应用程序可以将集算器脚本文件当做数据库存储过程执行,传入参数并用JDBC获得返回结果 下面举例说明集算器协助JAVA查询大文本的过程. 源数据sOrder.txt如下: 要查询起止时间是startDate.endDate之间,金额大于argA

Java查询大文本文件的处理方法

有时我们需要查询大文本而不是数据库,这时就需要流式读入文件并实现查询算法,还要进行并行处理以提高性能.但JAVA本身缺少相应的类库,需要硬编码才能实现结构化文件计算,代码复杂且可读性差,难以实现高效的并行处理. 使用免费的集算器可以弥补这一不足.集算器封装了丰富的结构化文件读写和游标计算函数,书写简单代码就能实现并行计算,并提供了易用的JDBC接口.JAVA应用程序可以将集算器脚本文件当做数据库存储过程执行,传入参数并用JDBC获得返回结果. 集算器与Java应用程序的集成结构如下: 下面举例说

Java十大低级错误

前言 本文档根据java开发人员在编码过程中容易忽视或经常出错的地方进行了整理,总结了十个比较常见的低级错误点,方便大家学习. Java十大低级错误 不能用"=="比较两个字符串内容相等. 对list做foreach循环时,循环代码中不能修改list的结构. 日志和实际情况不一致:捕获异常后没有在日志中记录异常栈. 魔鬼数字. 空指针异常. 数组下标越界. 将字符串转换为数字时没有捕获NumberFormatException异常. 对文件.IO.数据库等资源进行操作后没有及时.正确进

java filechannel大文件的读写

java读取大文件 超大文件的几种方法 转自:http://wgslucky.blog.163.com/blog/static/97562532201332324639689/ java 读取一个巨大的文本文件既能保证内存不溢出又能保证性能 2010-09-25 11:18:50|  分类: 默认分类 |字号 订阅 import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.

java实现大文件下载(http方式)

java实现大文件下载,基于http方式,控件神马的就不说了. 思路:下载文件无非要读取文件然后写文件,主要这两个步骤,主要难点: 1.读文件,就是硬盘到内存的过程,由于jdk内存限制,不能读的太大. 2.写文件,就是响应到浏览器端的过程,http协议是短链接,如果写文件太慢,时间过久,会造成浏览器死掉. 知识点: 1.org.apache.http.impl.client.CloseableHttpClient  模拟httpClient客户端发送http请求,可以控制到请求文件的字节位置.