最近因为更换了文件系统,需要进行相关的数据迁移。迁移过程需要从指定的资源服务器获取资源文件并迁移到新的资源服务器。

直接上关键代码

InputStream in = null;
byte[] data = null;


URL url = new URL("你的url");

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {

    //conn.getContentLength() 获取到读取的数据流大小
    log.info("文件名={},文件长度={}",fileName,conn.getContentLength());

    in = conn.getInputStream();

    data = new byte[conn.getContentLength()];
    int totalLeg = 0;
    int len = 0;
    //这里比较关键,如果读取到缓存的数据不正确可能会导致文件无法打开
    //这里的意思是从 0 开始读,每次读1024个字节;下次再从 totalLeg 的位置开始读;
    //read(byte b[], int off, int len).b为缓冲区;off为读取字节的下标;len为每次读取的长度
    while ((len = in.read(data, totalLeg, conn.getContentLength() - totalLeg > 1024 ? 1024 : conn.getContentLength() - totalLeg)) != -1) {

        totalLeg = totalLeg + len;
    }
    in.close();
    conn.disconnect();
}

其实还有一种解决办法:就是通过再服务器上写一个脚本,将收集到的指定目录下的文件名(因为路径是已知的)放在一个文本文件中,然后在本地通过代码去读取这个文本文件读出文件名,然后和路径拼接在一起再访问服务器上的资源文件。综合考虑了,发现这个有点复杂了,因为只是做个数据迁移,数据量并不大的情况下,暂时不考虑这个方案!

还有问题就是获取获取网络资源的流的时候,发现接收的字节大小并不是自己所希望的。这是因为网络通讯往往是间断性的,一串字节往往分几批进行发送。例如对方发来字节长度100的数据,本地程序调用available()方法有时得到0,有时得到50,有时能得到100,大多数情况下是100。这可能是对方还没有响应,也可能是对方已经响应了,但是数据还没有送达本地。也许分3批到达,也许分两批,也许一次性到达。具体参考:解决InputStream中数据读取不完整问题

多线程学习

并发编程3:线程池的使用与执行流程

java主线程等待所有子线程执行完毕在执行(常见面试题)

demo.java

public void demo() {

    ExecutorService executorService = Executors.newFixedThreadPool(5);

    for (int i = 0; i < 5; i++) {

        String threadName = "123";

        executorService.execute(new Runnable() {
            @Override
            public void run() {

                log.info("我是线程={}"/*,Thread.currentThread().getName()*/,threadName);
            }
        });

    }
}

由于考虑到仅仅靠main线程来获取并上传九百多的音频文件,感觉这个速度有点慢了。所以考虑通过线程池的方式来实现多线程并发的操作提升效率。

我用的是 java.util.concurrent 中的 ExecutorsnewFixedThreadPool

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
  • corePoolSize = nThreads
  • maximumPoolSize = nThreads
  • keepAliveTime = 0L
  • unit = TimeUnit.MILLISECONDS
  • workQueue = LinkedBlockingQueue<Runnable>

newFixedThreadPool() 方法会返回 ExecutorService ,这个接口有以下方法

Java通过url获取静态文件 java通过url获取文件流_java

由于我不用获取到每个线程的执行结果,所以用 execute() 方法就够了。不过我还是想先了解下线程执行结果的对象:Future

package java.util.concurrent;

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    //如果成功执行的话,get将会返回 null
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

我把 execute() 放在了 for 循环当中,期望执行 execute() n(n=子集合数) 次。可是我发现了一个问题,每当执行单元测试的方法的时候,main主线程都没有等待子线程执行完成就结束了。

我看到网上都说用 sleep() , join() 方法。感觉实践起来都不太好。所以最终选择了 CountdownLatch,其实它就是一个计数器,设置初始值。

调用了 CountdownLatch.await() 方法的线程将会被阻塞,直到 count == 0 为止。然后我在每个线程执行完成后调用了方法 countDown(),这样就可以让主线程等待子线程处理完成之后再继续了!

public void threadsDemo(){

    //拆分list
    List<List<String>> listGroup = new ArrayList<>();
    //每个子集合长度
    int toIndex = 25;
    for (int i = 0; i < fileNames.size(); i += 25) {

        if (i + 25 > fileNames.size()) {

            toIndex = fileNames.size() - i;
        }

        List<String> newSubList = fileNames.subList(i, i + toIndex);
        listGroup.add(newSubList);
    }
    
    //子集合数量
    log.info("length={}",listGroup.size());

    //计数器
    CountDownLatch countDownLatch = new CountDownLatch(listGroup.size());
    ExecutorService executorService = Executors.newFixedThreadPool(listGroup.size());
    for (List<String> each : listGroup) {

        executorService.execute(new Runnable() {
            @Override
            public void run() {

                for (String fileName : each) {

                    try {
                        InputStream in = null;
                        byte[] data = null;

                        URL url = new URL("你的url");

                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                        if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {

                            log.info("当前线程={},文件名={},文件长度={}",Thread.currentThread().getName(),fileName,conn.getContentLength());

                            in = conn.getInputStream();

                            data = new byte[conn.getContentLength()];
                            int totalLeg = 0;
                            int len = 0;
                            while ((len = in.read(data, totalLeg, conn.getContentLength() - totalLeg > 1024 ? 1024 : conn.getContentLength() - totalLeg)) != -1) {

                                totalLeg = totalLeg + len;

                            }
                            in.close();
                            conn.disconnect();

                            //业务方法
                            //....
                        }

                    } catch (Exception e) {
                        log.error("文件名={},error={}",fileName,e.getMessage());
                    }
                }

                countDownLatch.countDown();
                log.info("\n>>>>线程{}执行完成!\n",Thread.currentThread().getName());
            }
        });
    }

    countDownLatch.await();
}