Java 8 Stream,将List <file>转换为Map <integer,list <file =“”>&gt;

时间:2017-03-24 15:19:10

标签: java-8 java-stream

我在传统的java循环中有以下代码。想用Java 8 Stream代替。 我有一个排序的文件列表(按文件大小排序)。我将这些文件组合在一起,使所有文件的总大小不超过给定的最大大小,并将它们放在带有密钥1,2,3的Map中,等等。这是代码。

    List<File> allFilesSortedBySize = getListOfFiles();
    Map<Integer, List<File>> filesGroupedByMaxSizeMap = new HashMap<Integer, List<File>>();
    double totalLength = 0L;
    int count = 0;
    List<File> filesWithSizeTotalMaxSize = Lists.newArrayList();
    //group the files to be zipped together as per maximum allowable size in a map       
    for (File file : allFilesSortedBySize) {
        long sizeInBytes = file.length();
        double sizeInMb = (double)sizeInBytes / (1024 * 1024);
        totalLength = totalLength + sizeInMb;
        if(totalLength <= maxSize) {
            filesWithSizeTotalMaxSize.add(file);
        } else {
            count = count + 1;
            filesGroupedByMaxSizeMap.put(count, filesWithSizeTotalMaxSize);
            filesWithSizeTotalMaxSize = Lists.newArrayList();
            filesWithSizeTotalMaxSize.add(file);
            totalLength = sizeInMb;
        }
    }
    filesGroupedByMaxSizeMap.put(count+1, filesWithSizeTotalMaxSize);
    return filesGroupedByMaxSizeMap;

3 个答案:

答案 0 :(得分:3)

阅读后,我找到了使用Collectors.groupBy的解决方案。

使用java8 lambda表达式的代码

private final long MB = 1024 * 1024;

private Map<Integer, List<File>> grouping(List<File> files, long maxSize) {
    AtomicInteger group = new AtomicInteger(0);
    AtomicLong groupSize = new AtomicLong();
    return files.stream().collect(groupingBy((file) -> {
        if (groupSize.addAndGet(file.length()) <= maxSize * MB) {
            return group.get() == 0 ? group.incrementAndGet() : group.get();
        }
        groupSize.set(file.length());
        return group.incrementAndGet();
    }));
}

由@Holger提供的代码然后您可以自由地检查组是否等于0

private static final long MB = 1024 * 1024;


private Map<Integer, List<File>> grouping(List<File> files, long maxSize) {
    AtomicInteger group = new AtomicInteger(0);
    //force initializing group starts with 1 even if the first file is empty.
    AtomicLong groupSize = new AtomicLong(maxSize * MB + 1);

    return files.stream().collect(groupingBy((file) -> {
        if (groupSize.addAndGet(file.length()) <= maxSize * MB) {
            return group.get();
        }
        groupSize.set(file.length());
        return group.incrementAndGet();
    }));
}

使用匿名类的代码

受@Holger启发,使用修改外部状态的分组函数的所有“解决方案”都是滥用API的黑客攻击,因此您可以使用匿名类来管理类中的分组逻辑状态。

private static final long MB = 1024 * 1024;

private Map<Integer, List<File>> grouping(List<File> files, long maxSize) {
    return files.stream().collect(groupingBy(groupSize(maxSize)));
}

private Function<File, Integer> groupSize(final long maxSize) {
    long maxBytesSize = maxSize * MB;
    return new Function<File, Integer>() {
        private int group;
        private long groupSize = maxBytesSize + 1;

        @Override
        public Integer apply(File file) {
            return hasRemainingFor(file) ? current(file) : next(file);
        }

        private boolean hasRemainingFor(File file) {
            return (groupSize += file.length()) <= maxBytesSize;
        }

        private int next(File file) {
            groupSize = file.length();
            return ++group;
        }

        private int current(File file) {
            return group;
        }
    };
}

测试

import org.junit.jupiter.api.Test;

import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.groupingBy;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

/**
 * Created by holi on 3/24/17.
 */
public class StreamGroupingTest {


    private final File FILE_1MB = file(1);
    private final File FILE_2MB = file(2);
    private final File FILE_3MB = file(3);

    @Test
    void eachFileInIndividualGroupIfEachFileSizeGreaterThanMaxSize() {
        Map<Integer, List<File>> groups = grouping(asList(FILE_2MB, FILE_3MB), 1);

        assertThat(groups.size(), equalTo(2));
        assertThat(groups.get(1), equalTo(singletonList(FILE_2MB)));
        assertThat(groups.get(2), equalTo(singletonList(FILE_3MB)));
    }


    @Test
    void allFilesInAGroupIfTotalSizeOfFilesLessThanOrEqualMaxSize() {
        Map<Integer, List<File>> groups = grouping(asList(FILE_2MB, FILE_3MB), 5);

        assertThat(groups.size(), equalTo(1));
        assertThat(groups.get(1), equalTo(asList(FILE_2MB, FILE_3MB)));
    }

    @Test
    void allNeighboringFilesInAGroupThatTotalOfTheirSizeLessThanOrEqualMaxSize() {
        Map<Integer, List<File>> groups = grouping(asList(FILE_1MB, FILE_2MB, FILE_3MB), 3);

        assertThat(groups.size(), equalTo(2));
        assertThat(groups.get(1), equalTo(asList(FILE_1MB, FILE_2MB)));
        assertThat(groups.get(2), equalTo(singletonList(FILE_3MB)));
    }

    @Test
    void eachFileInIndividualGroupIfTheFirstFileAndTotalOfEachNeighboringFilesSizeGreaterThanMaxSize() {
        Map<Integer, List<File>> groups = grouping(asList(FILE_2MB, FILE_1MB, FILE_3MB), 2);

        assertThat(groups.size(), equalTo(3));
        assertThat(groups.get(1), equalTo(singletonList(FILE_2MB)));
        assertThat(groups.get(2), equalTo(singletonList(FILE_1MB)));
        assertThat(groups.get(3), equalTo(singletonList(FILE_3MB)));
    }

    @Test
    void theFirstEmptyFileInGroup1() throws Throwable {
        File emptyFile = file(0);

        Map<Integer, List<File>> groups = grouping(singletonList(emptyFile), 2);

        assertThat(groups.get(1), equalTo(singletonList(emptyFile)));
    }

    private static final long MB = 1024 * 1024;

    private Map<Integer, List<File>> grouping(List<File> files, long maxSize) {
        AtomicInteger group = new AtomicInteger(0);
        AtomicLong groupSize = new AtomicLong(maxSize * MB + 1);

        return files.stream().collect(groupingBy((file) -> {
            if (groupSize.addAndGet(file.length()) <= maxSize * MB) {
                return group.get();
            }
            groupSize.set(file.length());
            return group.incrementAndGet();
        }));
    }

    private Function<File, Integer> groupSize(final long maxSize) {
        long maxBytesSize = maxSize * MB;
        return new Function<File, Integer>() {
            private int group;
            private long groupSize = maxBytesSize + 1;

            @Override
            public Integer apply(File file) {
                return hasRemainingFor(file) ? current(file) : next(file);
            }

            private boolean hasRemainingFor(File file) {
                return (groupSize += file.length()) <= maxBytesSize;
            }

            private int next(File file) {
                groupSize = file.length();
                return ++group;
            }

            private int current(File file) {
                return group;
            }
        };
    }


    private File file(int sizeOfMB) {
        return new File(String.format("%dMB file", sizeOfMB)) {

            @Override
            public long length() {
                return sizeOfMB * MB;
            }

            @Override
            public boolean equals(Object obj) {
                File that = (File) obj;
                return length() == that.length();
            }
        };
    }

}

答案 1 :(得分:2)

由于每个元素的处理在很大程度上取决于之前的处理,因此该任务不适用于流。您仍然可以使用自定义收集器来实现它,但实现将比循环解决方案复杂得多。

换句话说,当您将其重写为流操作时,没有任何改进。继续循环。

但是,仍有一些事情可以改进。

List<File> allFilesSortedBySize = getListOfFiles();
// get maxSize in bytes ONCE, instead of converting EACH size to MiB
long maxSizeBytes = (long)(maxSize * 1024 * 1024);
// use "diamond operator"
Map<Integer, List<File>> filesGroupedByMaxSizeMap = new HashMap<>();
// start with "create new list" condition to avoid code duplication
long totalLength = maxSizeBytes;
// count is obsolete, the map maintains a size

// the initial "totalLength = maxSizeBytes" forces creating a new list within the loop
List<File> filesWithSizeTotalMaxSize = null;

for(File file: allFilesSortedBySize) {
    long length = file.length();
    if(maxSizeBytes-totalLength <= length) {
        filesWithSizeTotalMaxSize = new ArrayList<>(); // no utility method needed

        // store each list immediately, so no action after the loop needed
        filesGroupedByMaxSizeMap.put(filesGroupedByMaxSizeMap.size()+1,
                                     filesWithSizeTotalMaxSize);
        totalLength = 0;
    }
    totalLength += length;
    filesWithSizeTotalMaxSize.add(file);
}
return filesGroupedByMaxSizeMap;

您可以进一步替换

filesWithSizeTotalMaxSize = new ArrayList<>();
filesGroupedByMaxSizeMap.put(filesGroupedByMaxSizeMap.size()+1,
                             filesWithSizeTotalMaxSize);

filesWithSizeTotalMaxSize = filesGroupedByMaxSizeMap.computeIfAbsent(
        filesGroupedByMaxSizeMap.size()+1, x -> new ArrayList<>());

但是,这可能会有不同的意见,这是否有所改善。

答案 2 :(得分:1)

我能想到的问题最简单的解决方案是使用大小的AtomicInteger包装器和长度的List<File> files = getListOfFiles(); AtomicLong length = new AtomicLong(); AtomicInteger index = new AtomicInteger(1); long maxLength = SOME_ARBITRARY_NUMBER; Map<Integer, List<File>> collect = files.stream().collect(Collectors.groupingBy( file -> { if (length.addAndGet(file.length()) <= maxLength) { return index.get(); } length.set(file.length()); return index.incrementAndGet(); } )); return collect; 包装器。它们有一些有用的方法可以对它们执行基本的算术运算,这在这种特殊情况下非常有用。

Collectors.groupingBy

基本上this.router.navigate('#/students/profile/' + record.id); 是您所做的工作。